mirror of
https://github.com/openziti/zrok.git
synced 2024-11-07 08:44:14 +01:00
commit
309b146fd8
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -13,6 +13,7 @@ jobs:
|
||||
build-linux-amd64:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- run: sudo apt update
|
||||
- run: sudo apt-get install gcc-multilib g++-multilib
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
@ -54,6 +55,7 @@ jobs:
|
||||
build-linux-arm64:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- run: sudo apt update
|
||||
- run: sudo apt-get install gcc-aarch64-linux-gnu
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
@ -95,6 +97,7 @@ jobs:
|
||||
build-linux-arm:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- run: sudo apt update
|
||||
- run: sudo apt-get install gcc-arm-linux-gnueabi
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
@ -187,6 +190,7 @@ jobs:
|
||||
build-windows:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: sudo apt update
|
||||
- run: sudo apt-get install gcc-mingw-w64-x86-64
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@ -1,3 +1,27 @@
|
||||
# v0.4.0
|
||||
|
||||
FEATURE: New `tcpTunnel` backend mode allowing for private sharing of local TCP sockets with other `zrok` users (https://github.com/openziti/zrok/issues/170)
|
||||
|
||||
FEATURE: New `udpTunnel` backend mode allowing for private sharing of local UDP sockets with other `zrok` users (https://github.com/openziti/zrok/issues/306)
|
||||
|
||||
FEATURE: New metrics infrastructure based on OpenZiti usage events (https://github.com/openziti/zrok/issues/128). See the [v0.4 Metrics Guide](docs/guides/metrics-and-limits/configuring-metrics.md) for more information.
|
||||
|
||||
FEATURE: New limits implementation based on the new metrics infrastructure (https://github.com/openziti/zrok/issues/235). See the [v0.4 Limits Guide](docs/guides/metrics-and-limits/configuring-limits.md) for more information.
|
||||
|
||||
FEATURE: The invite mechanism has been reworked to improve user experience. The configuration has been updated to include a new `invite` stanza, and now includes a boolean flag indicating whether or not the instance allows new invitations to be created, and also includes contact details for requesting a new invite. These values are used by the `zrok invite` command to provide a smoother end-user invite experience https://github.com/openziti/zrok/issues/229)
|
||||
|
||||
FEATURE: New password strength checking rules and configuration. See the example configuration file (`etc/ctrl.yml`) for details about how to configure the strength checking rules (https://github.com/openziti/zrok/issues/167)
|
||||
|
||||
FEATURE: A new `admin/profile_endpoint` configuration option is available to start a `net/http/pprof` listener. See `etc/ctrl.yml` for details.
|
||||
|
||||
CHANGE: The controller configuration version bumps from `v: 2` to `v: 3` to support all of the new `v0.4` functionality. See the [example ctrl.yml](etc/ctrl.yml) for details on the new configuration.
|
||||
|
||||
CHANGE: The underlying database store now utilizes a `deleted` flag on all tables to implement "soft deletes". This was necessary for the new metrics infrastructure, where we need to account for metrics data that arrived after the lifetime of a share or environment; and also we're going to need this for limits, where we need to see historical information about activity in the past (https://github.com/openziti/zrok/issues/262)
|
||||
|
||||
CHANGE: Updated to latest `github.com/openziti/sdk-golang` (https://github.com/openziti/zrok/issues/335)
|
||||
|
||||
FIX: `zrok share reserved --override-endpoint` now works correctly; `--override-endpoint` was being incorrectly ignore previously (https://github.com/openziti/zrok/pull/348)
|
||||
|
||||
# v0.3.7
|
||||
|
||||
FIX: Improved TUI word-wrapping (https://github.com/openziti/zrok/issues/180)
|
||||
@ -46,7 +70,7 @@ CHANGE: Incorporate initial docker image build (https://github.com/openziti/zrok
|
||||
CHANGE: Improve target URL parsing for `zrok share` when using `--backend-mode` proxy (https://github.com/openziti/zrok/issues/211)
|
||||
|
||||
New and improved URL handling for proxy backends:
|
||||
|
||||
|
||||
9090 -> http://127.0.0.1:9090
|
||||
localhost:9090 -> http://127.0.0.1:9090
|
||||
https://localhost:9090 -> https://localhost:9090
|
||||
|
@ -36,7 +36,7 @@ See the [Concepts and Getting Started Guide](docs/getting-started.md) for a full
|
||||
|
||||
The single `zrok` binary contains everything you need to operate `zrok` environments and also host your own service instances. Just add an OpenZiti network and you're up and running.
|
||||
|
||||
See the [Self-Hosting Guide](docs/guides/v0.3_self_hosting_guide.md) for details on getting your own `zrok` service instance running. This builds on top of the [OpenZiti Quick Start](https://docs.openziti.io/docs/learn/quickstarts/network/) to have a running `zrok` service instance in minutes.
|
||||
See the [Self-Hosting Guide](docs/guides/self_hosting_guide.md) for details on getting your own `zrok` service instance running. This builds on top of the [OpenZiti Quick Start](https://docs.openziti.io/docs/learn/quickstarts/network/) to have a running `zrok` service instance in minutes.
|
||||
|
||||
## Building
|
||||
|
||||
|
@ -5,7 +5,7 @@ import "fmt"
|
||||
var Version string
|
||||
var Hash string
|
||||
|
||||
const Series = "v0.3"
|
||||
const Series = "v0.4"
|
||||
|
||||
func String() string {
|
||||
if Version != "" {
|
||||
|
@ -5,7 +5,9 @@ import (
|
||||
"github.com/go-openapi/runtime"
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/openziti/zrok/endpoints"
|
||||
"github.com/openziti/zrok/endpoints/privateFrontend"
|
||||
"github.com/openziti/zrok/endpoints/proxy"
|
||||
"github.com/openziti/zrok/endpoints/tcpTunnel"
|
||||
"github.com/openziti/zrok/endpoints/udpTunnel"
|
||||
"github.com/openziti/zrok/rest_client_zrok"
|
||||
"github.com/openziti/zrok/rest_client_zrok/share"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
@ -17,6 +19,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -45,14 +48,6 @@ func newAccessPrivateCommand() *accessPrivateCommand {
|
||||
func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
|
||||
shrToken := args[0]
|
||||
|
||||
endpointUrl, err := url.Parse("http://" + cmd.bindAddress)
|
||||
if err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("invalid endpoint address", err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
zrd, err := zrokdir.Load()
|
||||
if err != nil {
|
||||
tui.Error("unable to load zrokdir", err)
|
||||
@ -85,10 +80,89 @@ func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
|
||||
}
|
||||
logrus.Infof("allocated frontend '%v'", accessResp.Payload.FrontendToken)
|
||||
|
||||
cfg := privateFrontend.DefaultConfig("backend")
|
||||
cfg.ShrToken = shrToken
|
||||
cfg.Address = cmd.bindAddress
|
||||
cfg.RequestsChan = make(chan *endpoints.Request, 1024)
|
||||
protocol := "http://"
|
||||
switch accessResp.Payload.BackendMode {
|
||||
case "tcpTunnel":
|
||||
protocol = "tcp://"
|
||||
case "udpTunnel":
|
||||
protocol = "udp://"
|
||||
}
|
||||
|
||||
endpointUrl, err := url.Parse(protocol + cmd.bindAddress)
|
||||
if err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("invalid endpoint address", err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
requests := make(chan *endpoints.Request, 1024)
|
||||
switch accessResp.Payload.BackendMode {
|
||||
case "tcpTunnel":
|
||||
fe, err := tcpTunnel.NewFrontend(&tcpTunnel.FrontendConfig{
|
||||
BindAddress: cmd.bindAddress,
|
||||
IdentityName: "backend",
|
||||
ShrToken: args[0],
|
||||
RequestsChan: requests,
|
||||
})
|
||||
if err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("unable to create private frontend", err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
go func() {
|
||||
if err := fe.Run(); err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("error starting frontend", err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
case "udpTunnel":
|
||||
fe, err := udpTunnel.NewFrontend(&udpTunnel.FrontendConfig{
|
||||
BindAddress: cmd.bindAddress,
|
||||
IdentityName: "backend",
|
||||
ShrToken: args[0],
|
||||
RequestsChan: requests,
|
||||
IdleTime: time.Minute,
|
||||
})
|
||||
if err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("unable to create private frontend", err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
go func() {
|
||||
if err := fe.Run(); err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("error starting frontend", err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
default:
|
||||
cfg := proxy.DefaultFrontendConfig("backend")
|
||||
cfg.ShrToken = shrToken
|
||||
cfg.Address = cmd.bindAddress
|
||||
cfg.RequestsChan = requests
|
||||
fe, err := proxy.NewFrontend(cfg)
|
||||
if err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("unable to create private frontend", err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
go func() {
|
||||
if err := fe.Run(); err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("unable to run frontend", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
@ -98,27 +172,11 @@ func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
frontend, err := privateFrontend.NewHTTP(cfg)
|
||||
if err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("unable to create private frontend", err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := frontend.Run(); err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("unable to run frontend", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if cmd.headless {
|
||||
logrus.Infof("access the zrok share at the followind endpoint: %v", endpointUrl.String())
|
||||
for {
|
||||
select {
|
||||
case req := <-cfg.RequestsChan:
|
||||
case req := <-requests:
|
||||
logrus.Infof("%v -> %v %v", req.RemoteAddr, req.Method, req.Path)
|
||||
}
|
||||
}
|
||||
@ -132,7 +190,7 @@ func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case req := <-cfg.RequestsChan:
|
||||
case req := <-requests:
|
||||
if req != nil {
|
||||
prg.Send(req)
|
||||
}
|
||||
@ -144,17 +202,16 @@ func (cmd *accessPrivateCommand) run(_ *cobra.Command, args []string) {
|
||||
tui.Error("An error occurred", err)
|
||||
}
|
||||
|
||||
close(cfg.RequestsChan)
|
||||
close(requests)
|
||||
cmd.destroy(accessResp.Payload.FrontendToken, zrd.Env.ZId, shrToken, zrok, auth)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *accessPrivateCommand) destroy(frotendName, envZId, shrToken string, zrok *rest_client_zrok.Zrok, auth runtime.ClientAuthInfoWriter) {
|
||||
func (cmd *accessPrivateCommand) destroy(frontendName, envZId, shrToken string, zrok *rest_client_zrok.Zrok, auth runtime.ClientAuthInfoWriter) {
|
||||
logrus.Debugf("shutting down '%v'", shrToken)
|
||||
req := share.NewUnaccessParams()
|
||||
req.Body = &rest_model_zrok.UnaccessRequest{
|
||||
FrontendToken: frotendName,
|
||||
FrontendToken: frontendName,
|
||||
ShrToken: shrToken,
|
||||
EnvZID: envZId,
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/endpoints/publicFrontend"
|
||||
"github.com/openziti/zrok/endpoints/publicProxy"
|
||||
"github.com/openziti/zrok/tui"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -22,10 +22,9 @@ type accessPublicCommand struct {
|
||||
|
||||
func newAccessPublicCommand() *accessPublicCommand {
|
||||
cmd := &cobra.Command{
|
||||
Use: "public [<configPath>]",
|
||||
Aliases: []string{"fe"},
|
||||
Short: "Create a public access HTTP frontend",
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
Use: "public [<configPath>]",
|
||||
Short: "Create a public access HTTP frontend",
|
||||
Args: cobra.RangeArgs(0, 1),
|
||||
}
|
||||
command := &accessPublicCommand{cmd: cmd}
|
||||
cmd.Run = command.run
|
||||
@ -33,7 +32,7 @@ func newAccessPublicCommand() *accessPublicCommand {
|
||||
}
|
||||
|
||||
func (cmd *accessPublicCommand) run(_ *cobra.Command, args []string) {
|
||||
cfg := publicFrontend.DefaultConfig()
|
||||
cfg := publicProxy.DefaultConfig()
|
||||
if len(args) == 1 {
|
||||
if err := cfg.Load(args[0]); err != nil {
|
||||
if !panicInstead {
|
||||
@ -43,7 +42,7 @@ func (cmd *accessPublicCommand) run(_ *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
logrus.Infof(cf.Dump(cfg, cf.DefaultOptions()))
|
||||
frontend, err := publicFrontend.NewHTTP(cfg)
|
||||
frontend, err := publicProxy.NewHTTP(cfg)
|
||||
if err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("unable to create http frontend", err)
|
||||
|
@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/endpoints/publicFrontend"
|
||||
"github.com/openziti/zrok/endpoints/publicProxy"
|
||||
"github.com/openziti/zrok/tui"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -29,7 +29,7 @@ func newAccessPublicValidateCommand() *accessPublicValidateCommand {
|
||||
}
|
||||
|
||||
func (cmd *accessPublicValidateCommand) run(_ *cobra.Command, args []string) {
|
||||
cfg := publicFrontend.DefaultConfig()
|
||||
cfg := publicProxy.DefaultConfig()
|
||||
if err := cfg.Load(args[0]); err != nil {
|
||||
tui.Error(fmt.Sprintf("unable to load configuration '%v'", args[0]), err)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/controller"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -26,13 +27,13 @@ func newAdminBootstrap() *adminBootstrap {
|
||||
command := &adminBootstrap{cmd: cmd}
|
||||
cmd.Run = command.run
|
||||
cmd.Flags().BoolVar(&command.skipCtrl, "skip-ctrl", false, "Skip controller (ctrl) identity bootstrapping")
|
||||
cmd.Flags().BoolVar(&command.skipFrontend, "skip-frontend", false, "Slip frontend identity bootstrapping")
|
||||
cmd.Flags().BoolVar(&command.skipFrontend, "skip-frontend", false, "Skip frontend identity bootstrapping")
|
||||
return command
|
||||
}
|
||||
|
||||
func (cmd *adminBootstrap) run(_ *cobra.Command, args []string) {
|
||||
configPath := args[0]
|
||||
inCfg, err := controller.LoadConfig(configPath)
|
||||
inCfg, err := config.LoadConfig(configPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -21,10 +21,9 @@ type adminCreateFrontendCommand struct {
|
||||
|
||||
func newAdminCreateFrontendCommand() *adminCreateFrontendCommand {
|
||||
cmd := &cobra.Command{
|
||||
Use: "frontend <zitiId> <publicName> <urlTemplate>",
|
||||
Aliases: []string{"fe"},
|
||||
Short: "Create a global public frontend",
|
||||
Args: cobra.ExactArgs(3),
|
||||
Use: "frontend <zitiId> <publicName> <urlTemplate>",
|
||||
Short: "Create a global public frontend",
|
||||
Args: cobra.ExactArgs(3),
|
||||
}
|
||||
command := &adminCreateFrontendCommand{cmd: cmd}
|
||||
cmd.Run = command.run
|
||||
|
@ -18,10 +18,9 @@ type adminDeleteFrontendCommand struct {
|
||||
|
||||
func newAdminDeleteFrontendCommand() *adminDeleteFrontendCommand {
|
||||
cmd := &cobra.Command{
|
||||
Use: "frontend <frontendToken>",
|
||||
Aliases: []string{"fe"},
|
||||
Short: "Delete a global public frontend",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Use: "frontend <frontendToken>",
|
||||
Short: "Delete a global public frontend",
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
command := &adminDeleteFrontendCommand{cmd: cmd}
|
||||
cmd.Run = command.run
|
||||
|
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/controller"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -27,7 +28,7 @@ func newAdminGcCommand() *adminGcCommand {
|
||||
}
|
||||
|
||||
func (gc *adminGcCommand) run(_ *cobra.Command, args []string) {
|
||||
cfg, err := controller.LoadConfig(args[0])
|
||||
cfg, err := config.LoadConfig(args[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
41
cmd/zrok/adminMigrate.go
Normal file
41
cmd/zrok/adminMigrate.go
Normal file
@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
adminCmd.AddCommand(newAdminMigrate().cmd)
|
||||
}
|
||||
|
||||
type adminMigrate struct {
|
||||
cmd *cobra.Command
|
||||
}
|
||||
|
||||
func newAdminMigrate() *adminMigrate {
|
||||
cmd := &cobra.Command{
|
||||
Use: "migrate <configPath>",
|
||||
Short: "Migrate the underlying datastore",
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
command := &adminMigrate{cmd}
|
||||
cmd.Run = command.run
|
||||
return command
|
||||
}
|
||||
|
||||
func (cmd *adminMigrate) run(_ *cobra.Command, args []string) {
|
||||
configPath := args[0]
|
||||
inCfg, err := config.LoadConfig(configPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logrus.Info(cf.Dump(inCfg, cf.DefaultOptions()))
|
||||
if _, err := store.Open(inCfg.Store); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logrus.Info("migration complete")
|
||||
}
|
@ -3,14 +3,21 @@ package main
|
||||
import (
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/controller"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var controllerCmd *controllerCommand
|
||||
|
||||
var metricsCmd = &cobra.Command{
|
||||
Use: "metrics",
|
||||
Short: "Metrics related commands",
|
||||
}
|
||||
|
||||
func init() {
|
||||
controllerCmd = newControllerCommand()
|
||||
controllerCmd.cmd.AddCommand(metricsCmd)
|
||||
rootCmd.AddCommand(controllerCmd.cmd)
|
||||
}
|
||||
|
||||
@ -31,7 +38,7 @@ func newControllerCommand() *controllerCommand {
|
||||
}
|
||||
|
||||
func (cmd *controllerCommand) run(_ *cobra.Command, args []string) {
|
||||
cfg, err := controller.LoadConfig(args[0])
|
||||
cfg, err := config.LoadConfig(args[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
61
cmd/zrok/controllerMetricsBridge.go
Normal file
61
cmd/zrok/controllerMetricsBridge.go
Normal file
@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/openziti/zrok/controller/env"
|
||||
"github.com/openziti/zrok/controller/metrics"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
metricsCmd.AddCommand(newBridgeCommand().cmd)
|
||||
}
|
||||
|
||||
type bridgeCommand struct {
|
||||
cmd *cobra.Command
|
||||
}
|
||||
|
||||
func newBridgeCommand() *bridgeCommand {
|
||||
cmd := &cobra.Command{
|
||||
Use: "bridge <configPath>",
|
||||
Short: "Start a zrok metrics bridge",
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
command := &bridgeCommand{cmd}
|
||||
cmd.Run = command.run
|
||||
return command
|
||||
}
|
||||
|
||||
func (cmd *bridgeCommand) run(_ *cobra.Command, args []string) {
|
||||
cfg, err := config.LoadConfig(args[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logrus.Infof(cf.Dump(cfg, env.GetCfOptions()))
|
||||
|
||||
bridge, err := metrics.NewBridge(cfg.Bridge)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, err = bridge.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-c
|
||||
bridge.Stop()
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
for {
|
||||
time.Sleep(24 * 60 * time.Minute)
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/controller"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/openziti/zrok/tui"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -28,7 +28,7 @@ func newControllerValidateCommand() *controllerValidateCommand {
|
||||
}
|
||||
|
||||
func (cmd *controllerValidateCommand) run(_ *cobra.Command, args []string) {
|
||||
cfg, err := controller.LoadConfig(args[0])
|
||||
cfg, err := config.LoadConfig(args[0])
|
||||
if err != nil {
|
||||
tui.Error("controller config validation failed", err)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/openziti/zrok/rest_client_zrok/account"
|
||||
"github.com/openziti/zrok/rest_client_zrok/metadata"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/tui"
|
||||
"github.com/openziti/zrok/util"
|
||||
@ -21,9 +22,8 @@ func init() {
|
||||
}
|
||||
|
||||
type inviteCommand struct {
|
||||
cmd *cobra.Command
|
||||
token string
|
||||
tui inviteTui
|
||||
cmd *cobra.Command
|
||||
tui inviteTui
|
||||
}
|
||||
|
||||
func newInviteCommand() *inviteCommand {
|
||||
@ -38,8 +38,6 @@ func newInviteCommand() *inviteCommand {
|
||||
}
|
||||
cmd.Run = command.run
|
||||
|
||||
cmd.Flags().StringVar(&command.token, "token", "", "Invite token required when zrok running in token store mode")
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
@ -58,17 +56,33 @@ func (cmd *inviteCommand) run(_ *cobra.Command, _ []string) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
md, err := zrok.Metadata.Configuration(metadata.NewConfigurationParams())
|
||||
if err != nil {
|
||||
tui.Error("unable to get server metadata", err)
|
||||
}
|
||||
|
||||
if md != nil {
|
||||
if !md.GetPayload().InvitesOpen {
|
||||
apiEndpoint, _ := zrd.ApiEndpoint()
|
||||
tui.Error(fmt.Sprintf("'%v' is not currently accepting new users", apiEndpoint), nil)
|
||||
}
|
||||
cmd.tui.invitesOpen = md.GetPayload().InvitesOpen
|
||||
cmd.tui.RequiresInviteToken(md.GetPayload().RequiresInviteToken)
|
||||
cmd.tui.invitesContact = md.GetPayload().InviteTokenContact
|
||||
}
|
||||
|
||||
if _, err := tea.NewProgram(&cmd.tui).Run(); err != nil {
|
||||
tui.Error("unable to run interface", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if cmd.tui.done {
|
||||
email := cmd.tui.inputs[0].Value()
|
||||
email := cmd.tui.emailInputs[0].Value()
|
||||
token := cmd.tui.tokenInput.Value()
|
||||
|
||||
req := account.NewInviteParams()
|
||||
req.Body = &rest_model_zrok.InviteRequest{
|
||||
Email: email,
|
||||
Token: cmd.token,
|
||||
Token: token,
|
||||
}
|
||||
_, err = zrok.Account.Invite(req)
|
||||
if err != nil {
|
||||
@ -83,18 +97,22 @@ func (cmd *inviteCommand) run(_ *cobra.Command, _ []string) {
|
||||
func (cmd *inviteCommand) endpointError(apiEndpoint, _ string) {
|
||||
fmt.Printf("%v\n\n", tui.SeriousBusiness.Render("there was a problem creating an invitation!"))
|
||||
fmt.Printf("you are trying to use the zrok service at: %v\n\n", tui.Code.Render(apiEndpoint))
|
||||
fmt.Printf("%v\n\n", tui.Attention.Render("should you be using a --token? check with your instance administrator!"))
|
||||
fmt.Printf("you can change your zrok service endpoint using this command:\n\n")
|
||||
fmt.Printf("%v\n\n", tui.Code.Render("$ zrok config set apiEndpoint <newEndpoint>"))
|
||||
fmt.Printf("(where newEndpoint is something like: %v)\n\n", tui.Code.Render("https://some.zrok.io"))
|
||||
}
|
||||
|
||||
type inviteTui struct {
|
||||
focusIndex int
|
||||
msg string
|
||||
inputs []textinput.Model
|
||||
cursorMode textinput.CursorMode
|
||||
done bool
|
||||
focusIndex int
|
||||
msg string
|
||||
emailInputs []textinput.Model
|
||||
tokenInput textinput.Model
|
||||
cursorMode textinput.CursorMode
|
||||
done bool
|
||||
invitesOpen bool
|
||||
requireInviteToken bool
|
||||
invitesContact string
|
||||
maxIndex int
|
||||
|
||||
msgOk string
|
||||
msgMismatch string
|
||||
@ -110,7 +128,8 @@ type inviteTui struct {
|
||||
|
||||
func newInviteTui() inviteTui {
|
||||
m := inviteTui{
|
||||
inputs: make([]textinput.Model, 2),
|
||||
emailInputs: make([]textinput.Model, 2),
|
||||
maxIndex: 2,
|
||||
}
|
||||
m.focusedStyle = tui.Attention.Copy()
|
||||
m.blurredStyle = tui.Code.Copy()
|
||||
@ -125,7 +144,7 @@ func newInviteTui() inviteTui {
|
||||
m.msgMismatch = m.errorStyle.Render("email is invalid or does not match confirmation...")
|
||||
|
||||
var t textinput.Model
|
||||
for i := range m.inputs {
|
||||
for i := range m.emailInputs {
|
||||
t = textinput.New()
|
||||
t.CursorStyle = m.cursorStyle
|
||||
t.CharLimit = 96
|
||||
@ -140,9 +159,13 @@ func newInviteTui() inviteTui {
|
||||
t.Placeholder = "Confirm Email"
|
||||
}
|
||||
|
||||
m.inputs[i] = t
|
||||
m.emailInputs[i] = t
|
||||
}
|
||||
|
||||
m.tokenInput = textinput.New()
|
||||
m.tokenInput.CursorStyle = m.cursorStyle
|
||||
m.tokenInput.Placeholder = "Token"
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
@ -158,8 +181,8 @@ func (m *inviteTui) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case "tab", "shift+tab", "enter", "up", "down":
|
||||
s := msg.String()
|
||||
|
||||
if s == "enter" && m.focusIndex == len(m.inputs) {
|
||||
if util.IsValidEmail(m.inputs[0].Value()) && m.inputs[0].Value() == m.inputs[1].Value() {
|
||||
if s == "enter" && m.focusIndex == m.maxIndex {
|
||||
if util.IsValidEmail(m.emailInputs[0].Value()) && m.emailInputs[0].Value() == m.emailInputs[1].Value() {
|
||||
m.done = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
@ -175,23 +198,34 @@ func (m *inviteTui) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.focusIndex++
|
||||
}
|
||||
|
||||
if m.focusIndex > len(m.inputs) {
|
||||
if m.focusIndex > m.maxIndex {
|
||||
m.focusIndex = 0
|
||||
} else if m.focusIndex < 0 {
|
||||
m.focusIndex = len(m.inputs)
|
||||
m.focusIndex = m.maxIndex
|
||||
}
|
||||
|
||||
cmds := make([]tea.Cmd, len(m.inputs))
|
||||
for i := 0; i <= len(m.inputs)-1; i++ {
|
||||
cmds := make([]tea.Cmd, m.maxIndex)
|
||||
for i := 0; i <= len(m.emailInputs)-1; i++ {
|
||||
if i == m.focusIndex {
|
||||
cmds[i] = m.inputs[i].Focus()
|
||||
m.inputs[i].PromptStyle = m.focusedStyle
|
||||
m.inputs[i].TextStyle = m.focusedStyle
|
||||
cmds[i] = m.emailInputs[i].Focus()
|
||||
m.emailInputs[i].PromptStyle = m.focusedStyle
|
||||
m.emailInputs[i].TextStyle = m.focusedStyle
|
||||
continue
|
||||
}
|
||||
m.inputs[i].Blur()
|
||||
m.inputs[i].PromptStyle = m.noStyle
|
||||
m.inputs[i].TextStyle = m.noStyle
|
||||
m.emailInputs[i].Blur()
|
||||
m.emailInputs[i].PromptStyle = m.noStyle
|
||||
m.emailInputs[i].TextStyle = m.noStyle
|
||||
}
|
||||
if m.requireInviteToken {
|
||||
if m.focusIndex == 2 {
|
||||
cmds[2] = m.tokenInput.Focus()
|
||||
m.tokenInput.PromptStyle = m.focusedStyle
|
||||
m.tokenInput.TextStyle = m.focusedStyle
|
||||
} else {
|
||||
m.tokenInput.Blur()
|
||||
m.tokenInput.PromptStyle = m.noStyle
|
||||
m.tokenInput.TextStyle = m.noStyle
|
||||
}
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
@ -204,29 +238,49 @@ func (m *inviteTui) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (m *inviteTui) updateInputs(msg tea.Msg) tea.Cmd {
|
||||
cmds := make([]tea.Cmd, len(m.inputs))
|
||||
for i := range m.inputs {
|
||||
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
|
||||
cmds := make([]tea.Cmd, m.maxIndex)
|
||||
for i := range m.emailInputs {
|
||||
m.emailInputs[i], cmds[i] = m.emailInputs[i].Update(msg)
|
||||
}
|
||||
if m.requireInviteToken {
|
||||
m.tokenInput, cmds[2] = m.tokenInput.Update(msg)
|
||||
}
|
||||
return tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m inviteTui) View() string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(fmt.Sprintf("\n%v\n\n", m.msg))
|
||||
|
||||
for i := range m.inputs {
|
||||
b.WriteString(m.inputs[i].View())
|
||||
if i < len(m.inputs)-1 {
|
||||
b.WriteRune('\n')
|
||||
}
|
||||
if m.requireInviteToken && m.invitesContact != "" {
|
||||
b.WriteString(fmt.Sprintf("If you don't already have one, request an invite token at: %v\n\n", m.invitesContact))
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.emailInputs); i++ {
|
||||
b.WriteString(m.emailInputs[i].View())
|
||||
b.WriteRune('\n')
|
||||
}
|
||||
|
||||
if m.requireInviteToken {
|
||||
b.WriteString(m.tokenInput.View())
|
||||
b.WriteRune('\n')
|
||||
}
|
||||
|
||||
button := &m.blurredButton
|
||||
if m.focusIndex == len(m.inputs) {
|
||||
if m.focusIndex == m.maxIndex {
|
||||
button = &m.focusedButton
|
||||
}
|
||||
_, _ = fmt.Fprintf(&b, "\n\n%s\n\n", *button)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m *inviteTui) RequiresInviteToken(require bool) {
|
||||
m.requireInviteToken = require
|
||||
if require {
|
||||
m.maxIndex = 3
|
||||
} else {
|
||||
m.maxIndex = 2
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/michaelquigley/pfxlog"
|
||||
"github.com/openziti/transport/v2"
|
||||
"github.com/openziti/transport/v2/tcp"
|
||||
"github.com/openziti/transport/v2/udp"
|
||||
"github.com/openziti/zrok/tui"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -24,6 +27,8 @@ func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
rootCmd.AddCommand(shareCmd)
|
||||
rootCmd.AddCommand(testCmd)
|
||||
transport.AddAddressParser(tcp.AddressParser{})
|
||||
transport.AddAddressParser(udp.AddressParser{})
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
|
@ -33,7 +33,7 @@ func newReserveCommand() *reserveCommand {
|
||||
command := &reserveCommand{cmd: cmd}
|
||||
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
|
||||
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share")
|
||||
cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web}")
|
||||
cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web, <tcpTunnel, udpTunnel>}")
|
||||
cmd.Run = command.run
|
||||
return command
|
||||
}
|
||||
|
@ -6,8 +6,9 @@ import (
|
||||
"github.com/go-openapi/runtime"
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/openziti/zrok/endpoints"
|
||||
"github.com/openziti/zrok/endpoints/proxyBackend"
|
||||
"github.com/openziti/zrok/endpoints/webBackend"
|
||||
"github.com/openziti/zrok/endpoints/proxy"
|
||||
"github.com/openziti/zrok/endpoints/tcpTunnel"
|
||||
"github.com/openziti/zrok/endpoints/udpTunnel"
|
||||
"github.com/openziti/zrok/model"
|
||||
"github.com/openziti/zrok/rest_client_zrok"
|
||||
"github.com/openziti/zrok/rest_client_zrok/share"
|
||||
@ -43,7 +44,7 @@ func newSharePrivateCommand() *sharePrivateCommand {
|
||||
}
|
||||
command := &sharePrivateCommand{cmd: cmd}
|
||||
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...")
|
||||
cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web}")
|
||||
cmd.Flags().StringVar(&command.backendMode, "backend-mode", "proxy", "The backend mode {proxy, web, tcpTunnel, udpTunnel}")
|
||||
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
|
||||
cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for <target>")
|
||||
cmd.Run = command.run
|
||||
@ -67,8 +68,14 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
|
||||
case "web":
|
||||
target = args[0]
|
||||
|
||||
case "tcpTunnel":
|
||||
target = args[0]
|
||||
|
||||
case "udpTunnel":
|
||||
target = args[0]
|
||||
|
||||
default:
|
||||
tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web}", cmd.backendMode), nil)
|
||||
tui.Error(fmt.Sprintf("invalid backend mode '%v'; expected {proxy, web, tcpTunnel}", cmd.backendMode), nil)
|
||||
}
|
||||
|
||||
zrd, err := zrokdir.Load()
|
||||
@ -139,7 +146,7 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
|
||||
requestsChan := make(chan *endpoints.Request, 1024)
|
||||
switch cmd.backendMode {
|
||||
case "proxy":
|
||||
cfg := &proxyBackend.Config{
|
||||
cfg := &proxy.BackendConfig{
|
||||
IdentityPath: zif,
|
||||
EndpointAddress: target,
|
||||
ShrToken: resp.Payload.ShrToken,
|
||||
@ -155,7 +162,7 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
case "web":
|
||||
cfg := &webBackend.Config{
|
||||
cfg := &proxy.WebBackendConfig{
|
||||
IdentityPath: zif,
|
||||
WebRoot: target,
|
||||
ShrToken: resp.Payload.ShrToken,
|
||||
@ -169,6 +176,46 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
case "tcpTunnel":
|
||||
cfg := &tcpTunnel.BackendConfig{
|
||||
IdentityPath: zif,
|
||||
EndpointAddress: target,
|
||||
ShrToken: resp.Payload.ShrToken,
|
||||
RequestsChan: requestsChan,
|
||||
}
|
||||
be, err := tcpTunnel.NewBackend(cfg)
|
||||
if err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("unable to create tcpTunnel backend", err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
go func() {
|
||||
if err := be.Run(); err != nil {
|
||||
logrus.Errorf("error running tcpTunnel backend: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
case "udpTunnel":
|
||||
cfg := &udpTunnel.BackendConfig{
|
||||
IdentityPath: zif,
|
||||
EndpointAddress: target,
|
||||
ShrToken: resp.Payload.ShrToken,
|
||||
RequestsChan: requestsChan,
|
||||
}
|
||||
be, err := udpTunnel.NewBackend(cfg)
|
||||
if err != nil {
|
||||
if !panicInstead {
|
||||
tui.Error("unable to create udpTunnel backend", err)
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
go func() {
|
||||
if err := be.Run(); err != nil {
|
||||
logrus.Errorf("error running udpTunnel backend: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
default:
|
||||
tui.Error("invalid backend mode", nil)
|
||||
}
|
||||
@ -207,8 +254,8 @@ func (cmd *sharePrivateCommand) run(_ *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *sharePrivateCommand) proxyBackendMode(cfg *proxyBackend.Config) (endpoints.RequestHandler, error) {
|
||||
be, err := proxyBackend.NewBackend(cfg)
|
||||
func (cmd *sharePrivateCommand) proxyBackendMode(cfg *proxy.BackendConfig) (endpoints.RequestHandler, error) {
|
||||
be, err := proxy.NewBackend(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating http proxy backend")
|
||||
}
|
||||
@ -222,8 +269,8 @@ func (cmd *sharePrivateCommand) proxyBackendMode(cfg *proxyBackend.Config) (endp
|
||||
return be, nil
|
||||
}
|
||||
|
||||
func (cmd *sharePrivateCommand) webBackendMode(cfg *webBackend.Config) (endpoints.RequestHandler, error) {
|
||||
be, err := webBackend.NewBackend(cfg)
|
||||
func (cmd *sharePrivateCommand) webBackendMode(cfg *proxy.WebBackendConfig) (endpoints.RequestHandler, error) {
|
||||
be, err := proxy.NewWebBackend(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating http web backend")
|
||||
}
|
||||
|
@ -6,8 +6,7 @@ import (
|
||||
"github.com/go-openapi/runtime"
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/openziti/zrok/endpoints"
|
||||
"github.com/openziti/zrok/endpoints/proxyBackend"
|
||||
"github.com/openziti/zrok/endpoints/webBackend"
|
||||
"github.com/openziti/zrok/endpoints/proxy"
|
||||
"github.com/openziti/zrok/model"
|
||||
"github.com/openziti/zrok/rest_client_zrok"
|
||||
"github.com/openziti/zrok/rest_client_zrok/share"
|
||||
@ -142,7 +141,7 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
|
||||
requestsChan := make(chan *endpoints.Request, 1024)
|
||||
switch cmd.backendMode {
|
||||
case "proxy":
|
||||
cfg := &proxyBackend.Config{
|
||||
cfg := &proxy.BackendConfig{
|
||||
IdentityPath: zif,
|
||||
EndpointAddress: target,
|
||||
ShrToken: resp.Payload.ShrToken,
|
||||
@ -158,7 +157,7 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
case "web":
|
||||
cfg := &webBackend.Config{
|
||||
cfg := &proxy.WebBackendConfig{
|
||||
IdentityPath: zif,
|
||||
WebRoot: target,
|
||||
ShrToken: resp.Payload.ShrToken,
|
||||
@ -209,8 +208,8 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *sharePublicCommand) proxyBackendMode(cfg *proxyBackend.Config) (endpoints.RequestHandler, error) {
|
||||
be, err := proxyBackend.NewBackend(cfg)
|
||||
func (cmd *sharePublicCommand) proxyBackendMode(cfg *proxy.BackendConfig) (endpoints.RequestHandler, error) {
|
||||
be, err := proxy.NewBackend(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating http proxy backend")
|
||||
}
|
||||
@ -224,8 +223,8 @@ func (cmd *sharePublicCommand) proxyBackendMode(cfg *proxyBackend.Config) (endpo
|
||||
return be, nil
|
||||
}
|
||||
|
||||
func (cmd *sharePublicCommand) webBackendMode(cfg *webBackend.Config) (endpoints.RequestHandler, error) {
|
||||
be, err := webBackend.NewBackend(cfg)
|
||||
func (cmd *sharePublicCommand) webBackendMode(cfg *proxy.WebBackendConfig) (endpoints.RequestHandler, error) {
|
||||
be, err := proxy.NewWebBackend(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating http web backend")
|
||||
}
|
||||
|
@ -5,8 +5,7 @@ import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/openziti/zrok/endpoints"
|
||||
"github.com/openziti/zrok/endpoints/proxyBackend"
|
||||
"github.com/openziti/zrok/endpoints/webBackend"
|
||||
"github.com/openziti/zrok/endpoints/proxy"
|
||||
"github.com/openziti/zrok/rest_client_zrok/metadata"
|
||||
"github.com/openziti/zrok/rest_client_zrok/share"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
@ -74,6 +73,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
target = cmd.overrideEndpoint
|
||||
if target == "" {
|
||||
target = resp.Payload.BackendProxyEndpoint
|
||||
}
|
||||
@ -108,7 +108,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
|
||||
requestsChan := make(chan *endpoints.Request, 1024)
|
||||
switch resp.Payload.BackendMode {
|
||||
case "proxy":
|
||||
cfg := &proxyBackend.Config{
|
||||
cfg := &proxy.BackendConfig{
|
||||
IdentityPath: zif,
|
||||
EndpointAddress: target,
|
||||
ShrToken: shrToken,
|
||||
@ -124,7 +124,7 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
case "web":
|
||||
cfg := &webBackend.Config{
|
||||
cfg := &proxy.WebBackendConfig{
|
||||
IdentityPath: zif,
|
||||
WebRoot: target,
|
||||
ShrToken: shrToken,
|
||||
@ -187,8 +187,8 @@ func (cmd *shareReservedCommand) run(_ *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *shareReservedCommand) proxyBackendMode(cfg *proxyBackend.Config) (endpoints.RequestHandler, error) {
|
||||
be, err := proxyBackend.NewBackend(cfg)
|
||||
func (cmd *shareReservedCommand) proxyBackendMode(cfg *proxy.BackendConfig) (endpoints.RequestHandler, error) {
|
||||
be, err := proxy.NewBackend(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating http proxy backend")
|
||||
}
|
||||
@ -202,8 +202,8 @@ func (cmd *shareReservedCommand) proxyBackendMode(cfg *proxyBackend.Config) (end
|
||||
return be, nil
|
||||
}
|
||||
|
||||
func (cmd *shareReservedCommand) webBackendMode(cfg *webBackend.Config) (endpoints.RequestHandler, error) {
|
||||
be, err := webBackend.NewBackend(cfg)
|
||||
func (cmd *shareReservedCommand) webBackendMode(cfg *proxy.WebBackendConfig) (endpoints.RequestHandler, error) {
|
||||
be, err := proxy.NewWebBackend(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error creating http web backend")
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
"github.com/openziti/sdk-golang/ziti/config"
|
||||
"github.com/openziti/zrok/cmd/zrok/endpointUi"
|
||||
"github.com/openziti/zrok/tui"
|
||||
"github.com/pkg/errors"
|
||||
@ -79,13 +78,17 @@ func (cmd *testEndpointCommand) run(_ *cobra.Command, _ []string) {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
config := config.Config{}
|
||||
config := ziti.Config{}
|
||||
err = json.Unmarshal(identityJsonBytes, &config)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to load ziti configuration JSON: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
zitiContext := ziti.NewContextWithConfig(&config)
|
||||
zitiContext, err := ziti.NewContext(&config)
|
||||
if err != nil {
|
||||
fmt.Printf("error loading ziti context: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := zitiContext.Authenticate(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: Unable to authenticate ziti: %v\n\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"github.com/go-openapi/runtime"
|
||||
httptransport "github.com/go-openapi/runtime/client"
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
"github.com/openziti/sdk-golang/ziti/config"
|
||||
"github.com/openziti/sdk-golang/ziti/edge"
|
||||
"github.com/openziti/zrok/model"
|
||||
"github.com/openziti/zrok/rest_client_zrok"
|
||||
@ -144,7 +143,7 @@ func (l *looper) run() {
|
||||
}
|
||||
|
||||
func (l *looper) serviceListener() {
|
||||
zcfg, err := config.NewFromFile(l.zif)
|
||||
zcfg, err := ziti.NewConfigFromFile(l.zif)
|
||||
if err != nil {
|
||||
logrus.Errorf("error opening ziti config '%v': %v", l.zif, err)
|
||||
return
|
||||
@ -153,7 +152,12 @@ func (l *looper) serviceListener() {
|
||||
ConnectTimeout: 5 * time.Minute,
|
||||
MaxConnections: 10,
|
||||
}
|
||||
if l.listener, err = ziti.NewContextWithConfig(zcfg).ListenWithOptions(l.shrToken, &opts); err == nil {
|
||||
zctx, err := ziti.NewContext(zcfg)
|
||||
if err != nil {
|
||||
logrus.Errorf("error loading ziti context: %v", err)
|
||||
return
|
||||
}
|
||||
if l.listener, err = zctx.ListenWithOptions(l.shrToken, &opts); err == nil {
|
||||
if err := http.Serve(l.listener, l); err != nil {
|
||||
logrus.Errorf("looper #%d, error serving: %v", l.id, err)
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
"github.com/openziti/sdk-golang/ziti/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"nhooyr.io/websocket"
|
||||
@ -64,14 +63,17 @@ func (cmd *testWebsocketCommand) run(_ *cobra.Command, args []string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg := &config.Config{}
|
||||
cfg := &ziti.Config{}
|
||||
err = json.Unmarshal(identityJsonBytes, cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to load ziti configuration JSON: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
zitiContext := ziti.NewContextWithConfig(cfg)
|
||||
|
||||
zitiContext, err := ziti.NewContext(cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to load ziti context: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
dial := func(_ context.Context, _, addr string) (net.Conn, error) {
|
||||
service := strings.Split(addr, ":")[0]
|
||||
return zitiContext.Dial(service)
|
||||
|
@ -2,10 +2,12 @@ package controller
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/share"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -16,16 +18,16 @@ func newAccessHandler() *accessHandler {
|
||||
}
|
||||
|
||||
func (h *accessHandler) Handle(params share.AccessParams, principal *rest_model_zrok.Principal) middleware.Responder {
|
||||
tx, err := str.Begin()
|
||||
trx, err := str.Begin()
|
||||
if err != nil {
|
||||
logrus.Errorf("error starting transaction for user '%v': %v", principal.Email, err)
|
||||
return share.NewAccessInternalServerError()
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
defer func() { _ = trx.Rollback() }()
|
||||
|
||||
envZId := params.Body.EnvZID
|
||||
envId := 0
|
||||
if envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx); err == nil {
|
||||
if envs, err := str.FindEnvironmentsForAccount(int(principal.ID), trx); err == nil {
|
||||
found := false
|
||||
for _, env := range envs {
|
||||
if env.ZId == envZId {
|
||||
@ -45,28 +47,33 @@ func (h *accessHandler) Handle(params share.AccessParams, principal *rest_model_
|
||||
}
|
||||
|
||||
shrToken := params.Body.ShrToken
|
||||
sshr, err := str.FindShareWithToken(shrToken, tx)
|
||||
shr, err := str.FindShareWithToken(shrToken, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding share")
|
||||
return share.NewAccessNotFound()
|
||||
}
|
||||
if sshr == nil {
|
||||
if shr == nil {
|
||||
logrus.Errorf("unable to find share '%v' for user '%v'", shrToken, principal.Email)
|
||||
return share.NewAccessNotFound()
|
||||
}
|
||||
|
||||
if err := h.checkLimits(shr, trx); err != nil {
|
||||
logrus.Errorf("cannot access limited share for '%v': %v", principal.Email, err)
|
||||
return share.NewAccessNotFound()
|
||||
}
|
||||
|
||||
feToken, err := createToken()
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return share.NewAccessInternalServerError()
|
||||
}
|
||||
|
||||
if _, err := str.CreateFrontend(envId, &store.Frontend{Token: feToken, ZId: envZId}, tx); err != nil {
|
||||
if _, err := str.CreateFrontend(envId, &store.Frontend{PrivateShareId: &shr.Id, Token: feToken, ZId: envZId}, trx); err != nil {
|
||||
logrus.Errorf("error creating frontend record for user '%v': %v", principal.Email, err)
|
||||
return share.NewAccessInternalServerError()
|
||||
}
|
||||
|
||||
edge, err := edgeClient()
|
||||
edge, err := zrokEdgeSdk.Client(cfg.Ziti)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return share.NewAccessInternalServerError()
|
||||
@ -76,15 +83,31 @@ func (h *accessHandler) Handle(params share.AccessParams, principal *rest_model_
|
||||
"zrokFrontendToken": feToken,
|
||||
"zrokShareToken": shrToken,
|
||||
}
|
||||
if err := zrokEdgeSdk.CreateServicePolicyDial(envZId+"-"+sshr.ZId+"-dial", sshr.ZId, []string{envZId}, addlTags, edge); err != nil {
|
||||
if err := zrokEdgeSdk.CreateServicePolicyDial(feToken+"-"+envZId+"-"+shr.ZId+"-dial", shr.ZId, []string{envZId}, addlTags, edge); err != nil {
|
||||
logrus.Errorf("unable to create dial policy for user '%v': %v", principal.Email, err)
|
||||
return share.NewAccessInternalServerError()
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
if err := trx.Commit(); err != nil {
|
||||
logrus.Errorf("error committing frontend record: %v", err)
|
||||
return share.NewAccessInternalServerError()
|
||||
}
|
||||
|
||||
return share.NewAccessCreated().WithPayload(&rest_model_zrok.AccessResponse{FrontendToken: feToken})
|
||||
return share.NewAccessCreated().WithPayload(&rest_model_zrok.AccessResponse{
|
||||
FrontendToken: feToken,
|
||||
BackendMode: shr.BackendMode,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *accessHandler) checkLimits(shr *store.Share, trx *sqlx.Tx) error {
|
||||
if limitsAgent != nil {
|
||||
ok, err := limitsAgent.CanAccessShare(shr.Id, trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error checking share limits for '%v'", shr.Token)
|
||||
}
|
||||
if !ok {
|
||||
return errors.Errorf("share limit check failed for '%v'", shr.Token)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
55
controller/accountDetail.go
Normal file
55
controller/accountDetail.go
Normal file
@ -0,0 +1,55 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/metadata"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type accountDetailHandler struct{}
|
||||
|
||||
func newAccountDetailHandler() *accountDetailHandler {
|
||||
return &accountDetailHandler{}
|
||||
}
|
||||
|
||||
func (h *accountDetailHandler) Handle(params metadata.GetAccountDetailParams, principal *rest_model_zrok.Principal) middleware.Responder {
|
||||
trx, err := str.Begin()
|
||||
if err != nil {
|
||||
logrus.Errorf("error stasrting transaction for '%v': %v", principal.Email, err)
|
||||
return metadata.NewGetAccountDetailInternalServerError()
|
||||
}
|
||||
defer func() { _ = trx.Rollback() }()
|
||||
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error retrieving environments for '%v': %v", principal.Email, err)
|
||||
return metadata.NewGetAccountDetailInternalServerError()
|
||||
}
|
||||
sparkRx := make(map[int][]int64)
|
||||
sparkTx := make(map[int][]int64)
|
||||
if cfg.Metrics != nil && cfg.Metrics.Influx != nil {
|
||||
sparkRx, sparkTx, err = sparkDataForEnvironments(envs)
|
||||
if err != nil {
|
||||
logrus.Errorf("error querying spark data for environments for '%v': %v", principal.Email, err)
|
||||
}
|
||||
} else {
|
||||
logrus.Debug("skipping spark data for environments; no influx configuration")
|
||||
}
|
||||
var payload []*rest_model_zrok.Environment
|
||||
for _, env := range envs {
|
||||
var sparkData []*rest_model_zrok.SparkDataSample
|
||||
for i := 0; i < len(sparkRx[env.Id]) && i < len(sparkTx[env.Id]); i++ {
|
||||
sparkData = append(sparkData, &rest_model_zrok.SparkDataSample{Rx: float64(sparkRx[env.Id][i]), Tx: float64(sparkTx[env.Id][i])})
|
||||
}
|
||||
payload = append(payload, &rest_model_zrok.Environment{
|
||||
Activity: sparkData,
|
||||
Address: env.Address,
|
||||
CreatedAt: env.CreatedAt.UnixMilli(),
|
||||
Description: env.Description,
|
||||
Host: env.Host,
|
||||
UpdatedAt: env.UpdatedAt.UnixMilli(),
|
||||
ZID: env.ZId,
|
||||
})
|
||||
}
|
||||
return metadata.NewGetAccountDetailOK().WithPayload(payload)
|
||||
}
|
@ -5,17 +5,13 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/openziti/edge/rest_management_api_client"
|
||||
"github.com/openziti/edge/rest_management_api_client/config"
|
||||
"github.com/openziti/edge/rest_management_api_client/edge_router_policy"
|
||||
"github.com/openziti/edge/rest_management_api_client/identity"
|
||||
"github.com/openziti/edge/rest_management_api_client/service"
|
||||
"github.com/openziti/edge/rest_management_api_client/service_edge_router_policy"
|
||||
"github.com/openziti/edge/rest_management_api_client/service_policy"
|
||||
"github.com/openziti/edge/rest_model"
|
||||
rest_model_edge "github.com/openziti/edge/rest_model"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
"github.com/openziti/edge-api/rest_management_api_client/config"
|
||||
"github.com/openziti/edge-api/rest_management_api_client/edge_router_policy"
|
||||
"github.com/openziti/edge-api/rest_management_api_client/identity"
|
||||
rest_model_edge "github.com/openziti/edge-api/rest_model"
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
config2 "github.com/openziti/sdk-golang/ziti/config"
|
||||
zrok_config "github.com/openziti/zrok/controller/config"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/openziti/zrok/model"
|
||||
@ -25,7 +21,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func Bootstrap(skipCtrl, skipFrontend bool, inCfg *Config) error {
|
||||
func Bootstrap(skipCtrl, skipFrontend bool, inCfg *zrok_config.Config) error {
|
||||
cfg = inCfg
|
||||
|
||||
if v, err := store.Open(cfg.Store); err == nil {
|
||||
@ -35,7 +31,7 @@ func Bootstrap(skipCtrl, skipFrontend bool, inCfg *Config) error {
|
||||
}
|
||||
|
||||
logrus.Info("connecting to the ziti edge management api")
|
||||
edge, err := edgeClient()
|
||||
edge, err := zrokEdgeSdk.Client(cfg.Ziti)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error connecting to the ziti edge management api")
|
||||
}
|
||||
@ -100,27 +96,6 @@ func Bootstrap(skipCtrl, skipFrontend bool, inCfg *Config) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var metricsSvcZId string
|
||||
if metricsSvcZId, err = assertMetricsService(cfg, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := assertMetricsSerp(metricsSvcZId, cfg, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !skipCtrl {
|
||||
if err := assertCtrlMetricsBind(ctrlZId, metricsSvcZId, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !skipFrontend {
|
||||
if err := assertFrontendMetricsDial(frontendZId, metricsSvcZId, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -141,7 +116,7 @@ func assertZrokProxyConfigType(edge *rest_management_api_client.ZitiEdgeManageme
|
||||
}
|
||||
if len(listResp.Payload.Data) < 1 {
|
||||
name := model.ZrokProxyConfig
|
||||
ct := &rest_model.ConfigTypeCreate{Name: &name}
|
||||
ct := &rest_model_edge.ConfigTypeCreate{Name: &name}
|
||||
createReq := &config.CreateConfigTypeParams{ConfigType: ct}
|
||||
createReq.SetTimeout(30 * time.Second)
|
||||
createResp, err := edge.Config.CreateConfigType(createReq, nil)
|
||||
@ -162,16 +137,22 @@ func getIdentityId(identityName string) (string, error) {
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error opening identity '%v' from zrokdir", identityName)
|
||||
}
|
||||
zcfg, err := config2.NewFromFile(zif)
|
||||
zcfg, err := ziti.NewConfigFromFile(zif)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error loading ziti config from file '%v'", zif)
|
||||
}
|
||||
zctx := ziti.NewContextWithConfig(zcfg)
|
||||
zctx, err := ziti.NewContext(zcfg)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "error loading ziti context")
|
||||
}
|
||||
id, err := zctx.GetCurrentIdentity()
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error getting current identity from '%v'", zif)
|
||||
}
|
||||
return id.Id, nil
|
||||
if id.ID != nil {
|
||||
return *id.ID, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func assertIdentity(zId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
|
||||
@ -243,105 +224,3 @@ func assertErpForIdentity(name, zId string, edge *rest_management_api_client.Zit
|
||||
logrus.Infof("asserted erps for '%v' (%v)", name, zId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertMetricsService(cfg *Config, edge *rest_management_api_client.ZitiEdgeManagement) (string, error) {
|
||||
filter := fmt.Sprintf("name=\"%v\" and tags.zrok != null", cfg.Metrics.ServiceName)
|
||||
limit := int64(0)
|
||||
offset := int64(0)
|
||||
listReq := &service.ListServicesParams{
|
||||
Filter: &filter,
|
||||
Limit: &limit,
|
||||
Offset: &offset,
|
||||
}
|
||||
listReq.SetTimeout(30 * time.Second)
|
||||
listResp, err := edge.Service.ListServices(listReq, nil)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error listing '%v' service", cfg.Metrics.ServiceName)
|
||||
}
|
||||
var svcZId string
|
||||
if len(listResp.Payload.Data) != 1 {
|
||||
logrus.Infof("creating '%v' service", cfg.Metrics.ServiceName)
|
||||
svcZId, err = zrokEdgeSdk.CreateService("metrics", nil, nil, edge)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error creating '%v' service", cfg.Metrics.ServiceName)
|
||||
}
|
||||
} else {
|
||||
svcZId = *listResp.Payload.Data[0].ID
|
||||
}
|
||||
|
||||
logrus.Infof("asserted '%v' service (%v)", cfg.Metrics.ServiceName, svcZId)
|
||||
return svcZId, nil
|
||||
}
|
||||
|
||||
func assertMetricsSerp(metricsSvcZId string, cfg *Config, edge *rest_management_api_client.ZitiEdgeManagement) error {
|
||||
filter := fmt.Sprintf("allOf(serviceRoles) = \"@%v\" and allOf(edgeRouterRoles) = \"#all\" and tags.zrok != null", metricsSvcZId)
|
||||
limit := int64(0)
|
||||
offset := int64(0)
|
||||
listReq := &service_edge_router_policy.ListServiceEdgeRouterPoliciesParams{
|
||||
Filter: &filter,
|
||||
Limit: &limit,
|
||||
Offset: &offset,
|
||||
}
|
||||
listReq.SetTimeout(30 * time.Second)
|
||||
listResp, err := edge.ServiceEdgeRouterPolicy.ListServiceEdgeRouterPolicies(listReq, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error listing '%v' serps", cfg.Metrics.ServiceName)
|
||||
}
|
||||
if len(listResp.Payload.Data) != 1 {
|
||||
logrus.Infof("creating '%v' serp", cfg.Metrics.ServiceName)
|
||||
_, err := zrokEdgeSdk.CreateServiceEdgeRouterPolicy(cfg.Metrics.ServiceName, metricsSvcZId, nil, edge)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating '%v' serp", cfg.Metrics.ServiceName)
|
||||
}
|
||||
}
|
||||
logrus.Infof("asserted '%v' serp", cfg.Metrics.ServiceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertCtrlMetricsBind(ctrlZId, metricsSvcZId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
|
||||
filter := fmt.Sprintf("allOf(serviceRoles) = \"@%v\" and allOf(identityRoles) = \"@%v\" and type = 2 and tags.zrok != null", metricsSvcZId, ctrlZId)
|
||||
limit := int64(0)
|
||||
offset := int64(0)
|
||||
listReq := &service_policy.ListServicePoliciesParams{
|
||||
Filter: &filter,
|
||||
Limit: &limit,
|
||||
Offset: &offset,
|
||||
}
|
||||
listReq.SetTimeout(30 * time.Second)
|
||||
listResp, err := edge.ServicePolicy.ListServicePolicies(listReq, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error listing 'ctrl-metrics-bind' service policy")
|
||||
}
|
||||
if len(listResp.Payload.Data) != 1 {
|
||||
logrus.Info("creating 'ctrl-metrics-bind' service policy")
|
||||
if err = zrokEdgeSdk.CreateServicePolicyBind("ctrl-metrics-bind", metricsSvcZId, ctrlZId, nil, edge); err != nil {
|
||||
return errors.Wrap(err, "error creating 'ctrl-metrics-bind' service policy")
|
||||
}
|
||||
}
|
||||
logrus.Infof("asserted 'ctrl-metrics-bind' service policy")
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertFrontendMetricsDial(frontendZId, metricsSvcZId string, edge *rest_management_api_client.ZitiEdgeManagement) error {
|
||||
filter := fmt.Sprintf("allOf(serviceRoles) = \"@%v\" and allOf(identityRoles) = \"@%v\" and type = 1 and tags.zrok != null", metricsSvcZId, frontendZId)
|
||||
limit := int64(0)
|
||||
offset := int64(0)
|
||||
listReq := &service_policy.ListServicePoliciesParams{
|
||||
Filter: &filter,
|
||||
Limit: &limit,
|
||||
Offset: &offset,
|
||||
}
|
||||
listReq.SetTimeout(30 * time.Second)
|
||||
listResp, err := edge.ServicePolicy.ListServicePolicies(listReq, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error listing 'frontend-metrics-dial' service policy")
|
||||
}
|
||||
if len(listResp.Payload.Data) != 1 {
|
||||
logrus.Info("creating 'frontend-metrics-dial' service policy")
|
||||
if err = zrokEdgeSdk.CreateServicePolicyDial("frontend-metrics-dial", metricsSvcZId, []string{frontendZId}, nil, edge); err != nil {
|
||||
return errors.Wrap(err, "error creating 'frontend-metrics-dial' service policy")
|
||||
}
|
||||
}
|
||||
logrus.Infof("asserted 'frontend-metrics-dial' service policy")
|
||||
return nil
|
||||
}
|
||||
|
@ -1,33 +1,42 @@
|
||||
package controller
|
||||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/openziti/zrok/controller/emailUi"
|
||||
"github.com/openziti/zrok/controller/env"
|
||||
"github.com/openziti/zrok/controller/limits"
|
||||
"github.com/openziti/zrok/controller/metrics"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const ConfigVersion = 2
|
||||
const ConfigVersion = 3
|
||||
|
||||
type Config struct {
|
||||
V int
|
||||
Admin *AdminConfig
|
||||
Bridge *metrics.BridgeConfig
|
||||
Endpoint *EndpointConfig
|
||||
Email *EmailConfig
|
||||
Influx *InfluxConfig
|
||||
Limits *LimitsConfig
|
||||
Email *emailUi.Config
|
||||
Invites *InvitesConfig
|
||||
Limits *limits.Config
|
||||
Maintenance *MaintenanceConfig
|
||||
Metrics *MetricsConfig
|
||||
Metrics *metrics.Config
|
||||
Passwords *PasswordsConfig
|
||||
Registration *RegistrationConfig
|
||||
ResetPassword *ResetPasswordConfig
|
||||
Store *store.Config
|
||||
Ziti *ZitiConfig
|
||||
Ziti *zrokEdgeSdk.Config
|
||||
}
|
||||
|
||||
type AdminConfig struct {
|
||||
Secrets []string `cf:"+secret"`
|
||||
TouLink string
|
||||
Secrets []string `cf:"+secret"`
|
||||
TouLink string
|
||||
ProfileEndpoint string
|
||||
}
|
||||
|
||||
type EndpointConfig struct {
|
||||
@ -35,38 +44,10 @@ type EndpointConfig struct {
|
||||
Port int
|
||||
}
|
||||
|
||||
type EmailConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string `cf:"+secret"`
|
||||
From string
|
||||
}
|
||||
|
||||
type RegistrationConfig struct {
|
||||
RegistrationUrlTemplate string
|
||||
TokenStrategy string
|
||||
}
|
||||
|
||||
type ResetPasswordConfig struct {
|
||||
ResetUrlTemplate string
|
||||
}
|
||||
|
||||
type ZitiConfig struct {
|
||||
ApiEndpoint string
|
||||
Username string
|
||||
Password string `cf:"+secret"`
|
||||
}
|
||||
|
||||
type MetricsConfig struct {
|
||||
ServiceName string
|
||||
}
|
||||
|
||||
type InfluxConfig struct {
|
||||
Url string
|
||||
Bucket string
|
||||
Org string
|
||||
Token string `cf:"+secret"`
|
||||
type InvitesConfig struct {
|
||||
InvitesOpen bool
|
||||
TokenStrategy string
|
||||
TokenContact string
|
||||
}
|
||||
|
||||
type MaintenanceConfig struct {
|
||||
@ -74,6 +55,22 @@ type MaintenanceConfig struct {
|
||||
Registration *RegistrationMaintenanceConfig
|
||||
}
|
||||
|
||||
type PasswordsConfig struct {
|
||||
Length int
|
||||
RequireCapital bool
|
||||
RequireNumeric bool
|
||||
RequireSpecial bool
|
||||
ValidSpecialCharacters string
|
||||
}
|
||||
|
||||
type RegistrationConfig struct {
|
||||
RegistrationUrlTemplate string
|
||||
}
|
||||
|
||||
type ResetPasswordConfig struct {
|
||||
ResetUrlTemplate string
|
||||
}
|
||||
|
||||
type RegistrationMaintenanceConfig struct {
|
||||
ExpirationTimeout time.Duration
|
||||
CheckFrequency time.Duration
|
||||
@ -86,22 +83,9 @@ type ResetPasswordMaintenanceConfig struct {
|
||||
BatchLimit int
|
||||
}
|
||||
|
||||
const Unlimited = -1
|
||||
|
||||
type LimitsConfig struct {
|
||||
Environments int
|
||||
Shares int
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Limits: &LimitsConfig{
|
||||
Environments: Unlimited,
|
||||
Shares: Unlimited,
|
||||
},
|
||||
Metrics: &MetricsConfig{
|
||||
ServiceName: "metrics",
|
||||
},
|
||||
Limits: limits.DefaultConfig(),
|
||||
Maintenance: &MaintenanceConfig{
|
||||
ResetPassword: &ResetPasswordMaintenanceConfig{
|
||||
ExpirationTimeout: time.Minute * 15,
|
||||
@ -114,12 +98,19 @@ func DefaultConfig() *Config {
|
||||
BatchLimit: 500,
|
||||
},
|
||||
},
|
||||
Passwords: &PasswordsConfig{
|
||||
Length: 8,
|
||||
RequireCapital: true,
|
||||
RequireNumeric: true,
|
||||
RequireSpecial: true,
|
||||
ValidSpecialCharacters: `!@$&*_-., "#%'()+/:;<=>?[\]^{|}~`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
cfg := DefaultConfig()
|
||||
if err := cf.BindYaml(cfg, path, cf.DefaultOptions()); err != nil {
|
||||
if err := cf.BindYaml(cfg, path, env.GetCfOptions()); err != nil {
|
||||
return nil, errors.Wrapf(err, "error loading controller config '%v'", path)
|
||||
}
|
||||
if cfg.V != ConfigVersion {
|
@ -3,28 +3,41 @@ package controller
|
||||
import (
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/openziti/zrok/build"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/metadata"
|
||||
)
|
||||
|
||||
type configurationHandler struct {
|
||||
cfg *Config
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func newConfigurationHandler(cfg *Config) *configurationHandler {
|
||||
func newConfigurationHandler(cfg *config.Config) *configurationHandler {
|
||||
return &configurationHandler{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *configurationHandler) Handle(_ metadata.ConfigurationParams) middleware.Responder {
|
||||
tou := ""
|
||||
if cfg.Admin != nil {
|
||||
tou = cfg.Admin.TouLink
|
||||
}
|
||||
data := &rest_model_zrok.Configuration{
|
||||
Version: build.String(),
|
||||
TouLink: tou,
|
||||
Version: build.String(),
|
||||
InvitesOpen: cfg.Invites != nil && cfg.Invites.InvitesOpen,
|
||||
RequiresInviteToken: cfg.Invites != nil && cfg.Invites.TokenStrategy == "store",
|
||||
}
|
||||
if cfg.Admin != nil {
|
||||
data.TouLink = cfg.Admin.TouLink
|
||||
}
|
||||
if cfg.Invites != nil {
|
||||
data.InviteTokenContact = cfg.Invites.TokenContact
|
||||
}
|
||||
if cfg.Passwords != nil {
|
||||
data.PasswordRequirements = &rest_model_zrok.PasswordRequirements{
|
||||
Length: int64(cfg.Passwords.Length),
|
||||
RequireCapital: cfg.Passwords.RequireCapital,
|
||||
RequireNumeric: cfg.Passwords.RequireNumeric,
|
||||
RequireSpecial: cfg.Passwords.RequireSpecial,
|
||||
ValidSpecialCharacters: cfg.Passwords.ValidSpecialCharacters,
|
||||
}
|
||||
}
|
||||
return metadata.NewConfigurationOK().WithPayload(data)
|
||||
}
|
||||
|
@ -2,6 +2,13 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/openziti/zrok/controller/limits"
|
||||
"github.com/openziti/zrok/controller/metrics"
|
||||
"github.com/sirupsen/logrus"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/go-openapi/loads"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
@ -13,14 +20,20 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var cfg *Config
|
||||
var cfg *config.Config
|
||||
var str *store.Store
|
||||
var mtr *metricsAgent
|
||||
var idb influxdb2.Client
|
||||
var limitsAgent *limits.Agent
|
||||
|
||||
func Run(inCfg *Config) error {
|
||||
func Run(inCfg *config.Config) error {
|
||||
cfg = inCfg
|
||||
|
||||
if cfg.Admin != nil && cfg.Admin.ProfileEndpoint != "" {
|
||||
go func() {
|
||||
log.Println(http.ListenAndServe(cfg.Admin.ProfileEndpoint, nil))
|
||||
}()
|
||||
}
|
||||
|
||||
swaggerSpec, err := loads.Embedded(rest_server_zrok.SwaggerJSON, rest_server_zrok.FlatSwaggerJSON)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error loading embedded swagger spec")
|
||||
@ -30,8 +43,8 @@ func Run(inCfg *Config) error {
|
||||
api.KeyAuth = newZrokAuthenticator(cfg).authenticate
|
||||
api.AccountInviteHandler = newInviteHandler(cfg)
|
||||
api.AccountLoginHandler = account.LoginHandlerFunc(loginHandler)
|
||||
api.AccountRegisterHandler = newRegisterHandler()
|
||||
api.AccountResetPasswordHandler = newResetPasswordHandler()
|
||||
api.AccountRegisterHandler = newRegisterHandler(cfg)
|
||||
api.AccountResetPasswordHandler = newResetPasswordHandler(cfg)
|
||||
api.AccountResetPasswordRequestHandler = newResetPasswordRequestHandler()
|
||||
api.AccountVerifyHandler = newVerifyHandler()
|
||||
api.AdminCreateFrontendHandler = newCreateFrontendHandler()
|
||||
@ -40,15 +53,22 @@ func Run(inCfg *Config) error {
|
||||
api.AdminInviteTokenGenerateHandler = newInviteTokenGenerateHandler()
|
||||
api.AdminListFrontendsHandler = newListFrontendsHandler()
|
||||
api.AdminUpdateFrontendHandler = newUpdateFrontendHandler()
|
||||
api.EnvironmentEnableHandler = newEnableHandler(cfg.Limits)
|
||||
api.EnvironmentEnableHandler = newEnableHandler()
|
||||
api.EnvironmentDisableHandler = newDisableHandler()
|
||||
api.MetadataGetAccountDetailHandler = newAccountDetailHandler()
|
||||
api.MetadataConfigurationHandler = newConfigurationHandler(cfg)
|
||||
if cfg.Metrics != nil && cfg.Metrics.Influx != nil {
|
||||
api.MetadataGetAccountMetricsHandler = newGetAccountMetricsHandler(cfg.Metrics.Influx)
|
||||
api.MetadataGetEnvironmentMetricsHandler = newGetEnvironmentMetricsHandler(cfg.Metrics.Influx)
|
||||
api.MetadataGetShareMetricsHandler = newGetShareMetricsHandler(cfg.Metrics.Influx)
|
||||
}
|
||||
api.MetadataGetEnvironmentDetailHandler = newEnvironmentDetailHandler()
|
||||
api.MetadataGetFrontendDetailHandler = newGetFrontendDetailHandler()
|
||||
api.MetadataGetShareDetailHandler = newShareDetailHandler()
|
||||
api.MetadataOverviewHandler = metadata.OverviewHandlerFunc(overviewHandler)
|
||||
api.MetadataOverviewHandler = newOverviewHandler()
|
||||
api.MetadataVersionHandler = metadata.VersionHandlerFunc(versionHandler)
|
||||
api.ShareAccessHandler = newAccessHandler()
|
||||
api.ShareShareHandler = newShareHandler(cfg.Limits)
|
||||
api.ShareShareHandler = newShareHandler()
|
||||
api.ShareUnaccessHandler = newUnaccessHandler()
|
||||
api.ShareUnshareHandler = newUnshareHandler()
|
||||
api.ShareUpdateShareHandler = newUpdateShareHandler()
|
||||
@ -63,17 +83,31 @@ func Run(inCfg *Config) error {
|
||||
return errors.Wrap(err, "error opening store")
|
||||
}
|
||||
|
||||
if cfg.Influx != nil {
|
||||
idb = influxdb2.NewClient(cfg.Influx.Url, cfg.Influx.Token)
|
||||
if cfg.Metrics != nil && cfg.Metrics.Influx != nil {
|
||||
idb = influxdb2.NewClient(cfg.Metrics.Influx.Url, cfg.Metrics.Influx.Token)
|
||||
} else {
|
||||
logrus.Warn("skipping influx client; no configuration")
|
||||
}
|
||||
|
||||
if cfg.Metrics != nil {
|
||||
mtr = newMetricsAgent()
|
||||
go mtr.run()
|
||||
defer func() {
|
||||
mtr.stop()
|
||||
mtr.join()
|
||||
}()
|
||||
if cfg.Metrics != nil && cfg.Metrics.Agent != nil && cfg.Metrics.Influx != nil {
|
||||
ma, err := metrics.NewAgent(cfg.Metrics.Agent, str, cfg.Metrics.Influx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating metrics agent")
|
||||
}
|
||||
if err := ma.Start(); err != nil {
|
||||
return errors.Wrap(err, "error starting metrics agent")
|
||||
}
|
||||
defer func() { ma.Stop() }()
|
||||
|
||||
if cfg.Limits != nil && cfg.Limits.Enforcing {
|
||||
limitsAgent, err = limits.NewAgent(cfg.Limits, cfg.Metrics.Influx, cfg.Ziti, cfg.Email, str)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating limits agent")
|
||||
}
|
||||
ma.AddUsageSink(limitsAgent)
|
||||
limitsAgent.Start()
|
||||
defer func() { limitsAgent.Stop() }()
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
@ -25,7 +25,7 @@ func (h *createFrontendHandler) Handle(params admin.CreateFrontendParams, princi
|
||||
return admin.NewCreateFrontendUnauthorized()
|
||||
}
|
||||
|
||||
client, err := edgeClient()
|
||||
client, err := zrokEdgeSdk.Client(cfg.Ziti)
|
||||
if err != nil {
|
||||
logrus.Errorf("error getting edge client: %v", err)
|
||||
return admin.NewCreateFrontendInternalServerError()
|
||||
|
@ -3,15 +3,12 @@ package controller
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/openziti/edge/rest_management_api_client/service"
|
||||
rest_model_edge "github.com/openziti/edge/rest_model"
|
||||
rest_model_edge "github.com/openziti/edge-api/rest_model"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/admin"
|
||||
"github.com/sirupsen/logrus"
|
||||
"time"
|
||||
)
|
||||
|
||||
type createIdentityHandler struct{}
|
||||
@ -28,7 +25,7 @@ func (h *createIdentityHandler) Handle(params admin.CreateIdentityParams, princi
|
||||
return admin.NewCreateIdentityUnauthorized()
|
||||
}
|
||||
|
||||
edge, err := edgeClient()
|
||||
edge, err := zrokEdgeSdk.Client(cfg.Ziti)
|
||||
if err != nil {
|
||||
logrus.Errorf("error getting edge client: %v", err)
|
||||
return admin.NewCreateIdentityInternalServerError()
|
||||
@ -52,32 +49,6 @@ func (h *createIdentityHandler) Handle(params admin.CreateIdentityParams, princi
|
||||
return admin.NewCreateIdentityInternalServerError()
|
||||
}
|
||||
|
||||
filter := fmt.Sprintf("name=\"%v\" and tags.zrok != null", cfg.Metrics.ServiceName)
|
||||
limit := int64(0)
|
||||
offset := int64(0)
|
||||
listSvcReq := &service.ListServicesParams{
|
||||
Filter: &filter,
|
||||
Limit: &limit,
|
||||
Offset: &offset,
|
||||
}
|
||||
listSvcReq.SetTimeout(30 * time.Second)
|
||||
listSvcResp, err := edge.Service.ListServices(listSvcReq, nil)
|
||||
if err != nil {
|
||||
logrus.Errorf("error listing metrics service: %v", err)
|
||||
return admin.NewCreateIdentityInternalServerError()
|
||||
}
|
||||
if len(listSvcResp.Payload.Data) != 1 {
|
||||
logrus.Errorf("could not find metrics service")
|
||||
return admin.NewCreateIdentityInternalServerError()
|
||||
}
|
||||
svcZId := *listSvcResp.Payload.Data[0].ID
|
||||
|
||||
spName := fmt.Sprintf("%v-%v-dial", name, cfg.Metrics.ServiceName)
|
||||
if err := zrokEdgeSdk.CreateServicePolicyDial(spName, svcZId, []string{zId}, nil, edge); err != nil {
|
||||
logrus.Errorf("error creating named dial service policy '%v': %v", spName, err)
|
||||
return admin.NewCreateIdentityInternalServerError()
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
enc := json.NewEncoder(&out)
|
||||
enc.SetEscapeHTML(false)
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/edge/rest_management_api_client"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/environment"
|
||||
@ -36,7 +36,11 @@ func (h *disableHandler) Handle(params environment.DisableParams, principal *res
|
||||
logrus.Errorf("error getting environment for user '%v': %v", principal.Email, err)
|
||||
return environment.NewDisableInternalServerError()
|
||||
}
|
||||
edge, err := edgeClient()
|
||||
if env.Deleted {
|
||||
logrus.Errorf("environment '%v' for user '%v' deleted", env.ZId, principal.Email)
|
||||
return environment.NewDisableUnauthorized()
|
||||
}
|
||||
edge, err := zrokEdgeSdk.Client(cfg.Ziti)
|
||||
if err != nil {
|
||||
logrus.Errorf("error getting edge client for user '%v': %v", principal.Email, err)
|
||||
return environment.NewDisableInternalServerError()
|
||||
@ -85,29 +89,31 @@ func (h *disableHandler) removeSharesForEnvironment(envId int, tx *sqlx.Tx, edge
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shrs, err := str.FindSharesForEnvironment(envId, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, shr := range shrs {
|
||||
shrToken := shr.Token
|
||||
logrus.Infof("garbage collecting share '%v' for environment '%v'", shrToken, env.ZId)
|
||||
if err := zrokEdgeSdk.DeleteServiceEdgeRouterPolicy(env.ZId, shrToken, edge); err != nil {
|
||||
logrus.Error(err)
|
||||
if !env.Deleted {
|
||||
shrs, err := str.FindSharesForEnvironment(envId, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteServicePolicyDial(env.ZId, shrToken, edge); err != nil {
|
||||
logrus.Error(err)
|
||||
for _, shr := range shrs {
|
||||
shrToken := shr.Token
|
||||
logrus.Infof("garbage collecting share '%v' for environment '%v'", shrToken, env.ZId)
|
||||
if err := zrokEdgeSdk.DeleteServiceEdgeRouterPolicy(env.ZId, shrToken, edge); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteServicePoliciesDial(env.ZId, shrToken, edge); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteServicePoliciesBind(env.ZId, shrToken, edge); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteConfig(env.ZId, shrToken, edge); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteService(env.ZId, shr.ZId, edge); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
logrus.Infof("removed share '%v' for environment '%v'", shr.Token, env.ZId)
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteServicePolicyBind(env.ZId, shrToken, edge); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteConfig(env.ZId, shrToken, edge); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteService(env.ZId, shr.ZId, edge); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
logrus.Infof("removed share '%v' for environment '%v'", shr.Token, env.ZId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -117,13 +123,15 @@ func (h *disableHandler) removeFrontendsForEnvironment(envId int, tx *sqlx.Tx, e
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fes, err := str.FindFrontendsForEnvironment(envId, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fe := range fes {
|
||||
if err := zrokEdgeSdk.DeleteServicePolicy(env.ZId, fmt.Sprintf("tags.zrokFrontendToken=\"%v\" and type=1", fe.Token), edge); err != nil {
|
||||
logrus.Errorf("error removing frontend access for '%v': %v", fe.Token, err)
|
||||
if !env.Deleted {
|
||||
fes, err := str.FindFrontendsForEnvironment(envId, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fe := range fes {
|
||||
if err := zrokEdgeSdk.DeleteServicePolicies(env.ZId, fmt.Sprintf("tags.zrokFrontendToken=\"%v\" and type=1", fe.Token), edge); err != nil {
|
||||
logrus.Errorf("error removing frontend access for '%v': %v", fe.Token, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
9
controller/emailUi/config.go
Normal file
9
controller/emailUi/config.go
Normal file
@ -0,0 +1,9 @@
|
||||
package emailUi
|
||||
|
||||
type Config struct {
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string `cf:"+secret"`
|
||||
From string
|
||||
}
|
@ -2,5 +2,5 @@ package emailUi
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed verify.gohtml verify.gotext resetPassword.gohtml resetPassword.gotext
|
||||
//go:embed verify.gohtml verify.gotext resetPassword.gohtml resetPassword.gotext limitWarning.gohtml limitWarning.gotext
|
||||
var FS embed.FS
|
||||
|
156
controller/emailUi/limitWarning.gohtml
Normal file
156
controller/emailUi/limitWarning.gohtml
Normal file
@ -0,0 +1,156 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Transfer limit warning!</title>
|
||||
<meta name="description" content="zrok Transfer Limit Warning">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 25;
|
||||
font-family: 'JetBrains Mono', 'Courier New', monospace;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #ffffff;
|
||||
background-color: #3b2693;
|
||||
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #00d7e4;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #00d7e4;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:active {
|
||||
color: #ff0100;
|
||||
}
|
||||
|
||||
.claim {
|
||||
font-size: 2em;
|
||||
margin: 0.5em 0 1em 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 62em;
|
||||
margin: 2em auto;
|
||||
max-width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
margin: .25em;
|
||||
padding: 10px 16px;
|
||||
font-size: 1.15em;
|
||||
line-height: 1.33;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #ffffff;
|
||||
background-color: #ff0100;
|
||||
border-color: #ff0100;
|
||||
}
|
||||
|
||||
a.btn-primary:link,
|
||||
a.btn-primary:visited {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
a.btn-primary:hover,
|
||||
a.btn-primary:active {
|
||||
background-color: #cf0100;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #b3b3b3;
|
||||
border-color: #b3b3b3;
|
||||
color: #252525;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
a.btn-secondary:link,
|
||||
a.btn-secondary:visited {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
a.btn-secondary:hover,
|
||||
a.btn-secondary:hover {
|
||||
background-color: #ccc;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.about {
|
||||
margin: 1em auto;
|
||||
}
|
||||
|
||||
.about td {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.about td:first-child {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
img {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
body {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 320px) {
|
||||
body {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="font-family: 'JetBrains Mono', 'Courier New', monospace; color: #ffffff; background-color: #3b2693; font-weight: 600;">
|
||||
|
||||
<div class="container">
|
||||
<div class="banner" style="margin: auto;">
|
||||
<img src="https://zrok.io/wp-content/uploads/2023/03/warning.jpg" width="363px" height="500px" style="padding-bottom: 10px;"/>
|
||||
</div>
|
||||
<div class="cta" style="text-align: center;">
|
||||
<h3 style="text-align: center;">Your account is reaching a transfer limit, {{ .EmailAddress }}.</h3>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ .Detail }}
|
||||
</div>
|
||||
|
||||
<table border="0" cellpadding="0" cellspacing="0" align="center" class="about">
|
||||
<tr>
|
||||
<td><a href="https://github.com/openziti/zrok" target="_blank">github.com/openziti/zrok</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ .Version }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="text-align: center;"></a>Copyright © 2023 <a href="http://www.netfoundry.io" target="_blank" style="color: #00d7e4;">NetFoundry, Inc.</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
3
controller/emailUi/limitWarning.gotext
Normal file
3
controller/emailUi/limitWarning.gotext
Normal file
@ -0,0 +1,3 @@
|
||||
Your account is nearing a transfer size limit, {{ .EmailAddress }}!
|
||||
|
||||
{{ .Detail }}
|
25
controller/emailUi/model.go
Normal file
25
controller/emailUi/model.go
Normal file
@ -0,0 +1,25 @@
|
||||
package emailUi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/pkg/errors"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type WarningEmail struct {
|
||||
EmailAddress string
|
||||
Detail string
|
||||
Version string
|
||||
}
|
||||
|
||||
func (we WarningEmail) MergeTemplate(filename string) (string, error) {
|
||||
t, err := template.ParseFS(FS, filename)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error parsing warning email template '%v'", filename)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := t.Execute(buf, we); err != nil {
|
||||
return "", errors.Wrapf(err, "error executing warning email template '%v'", filename)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
@ -13,29 +13,27 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type enableHandler struct {
|
||||
cfg *LimitsConfig
|
||||
}
|
||||
type enableHandler struct{}
|
||||
|
||||
func newEnableHandler(cfg *LimitsConfig) *enableHandler {
|
||||
return &enableHandler{cfg: cfg}
|
||||
func newEnableHandler() *enableHandler {
|
||||
return &enableHandler{}
|
||||
}
|
||||
|
||||
func (h *enableHandler) Handle(params environment.EnableParams, principal *rest_model_zrok.Principal) middleware.Responder {
|
||||
// start transaction early; if it fails, don't bother creating ziti resources
|
||||
tx, err := str.Begin()
|
||||
trx, err := str.Begin()
|
||||
if err != nil {
|
||||
logrus.Errorf("error starting transaction for user '%v': %v", principal.Email, err)
|
||||
return environment.NewEnableInternalServerError()
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
defer func() { _ = trx.Rollback() }()
|
||||
|
||||
if err := h.checkLimits(principal, tx); err != nil {
|
||||
if err := h.checkLimits(principal, trx); err != nil {
|
||||
logrus.Errorf("limits error for user '%v': %v", principal.Email, err)
|
||||
return environment.NewEnableUnauthorized()
|
||||
}
|
||||
|
||||
client, err := edgeClient()
|
||||
client, err := zrokEdgeSdk.Client(cfg.Ziti)
|
||||
if err != nil {
|
||||
logrus.Errorf("error getting edge client for user '%v': %v", principal.Email, err)
|
||||
return environment.NewEnableInternalServerError()
|
||||
@ -70,14 +68,14 @@ func (h *enableHandler) Handle(params environment.EnableParams, principal *rest_
|
||||
Host: params.Body.Host,
|
||||
Address: realRemoteAddress(params.HTTPRequest),
|
||||
ZId: envZId,
|
||||
}, tx)
|
||||
}, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error storing created identity for user '%v': %v", principal.Email, err)
|
||||
_ = tx.Rollback()
|
||||
_ = trx.Rollback()
|
||||
return environment.NewEnableInternalServerError()
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
if err := trx.Commit(); err != nil {
|
||||
logrus.Errorf("error committing for user '%v': %v", principal.Email, err)
|
||||
return environment.NewEnableInternalServerError()
|
||||
}
|
||||
@ -99,14 +97,16 @@ func (h *enableHandler) Handle(params environment.EnableParams, principal *rest_
|
||||
return resp
|
||||
}
|
||||
|
||||
func (h *enableHandler) checkLimits(principal *rest_model_zrok.Principal, tx *sqlx.Tx) error {
|
||||
if !principal.Limitless && h.cfg.Environments > Unlimited {
|
||||
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx)
|
||||
if err != nil {
|
||||
return errors.Errorf("unable to find environments for account '%v': %v", principal.Email, err)
|
||||
}
|
||||
if len(envs)+1 > h.cfg.Environments {
|
||||
return errors.Errorf("would exceed environments limit of %d for '%v'", h.cfg.Environments, principal.Email)
|
||||
func (h *enableHandler) checkLimits(principal *rest_model_zrok.Principal, trx *sqlx.Tx) error {
|
||||
if !principal.Limitless {
|
||||
if limitsAgent != nil {
|
||||
ok, err := limitsAgent.CanCreateEnvironment(int(principal.ID), trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error checking environment limits for '%v'", principal.Email)
|
||||
}
|
||||
if !ok {
|
||||
return errors.Errorf("environment limit check failed for '%v'", principal.Email)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
14
controller/env/cf.go
vendored
Normal file
14
controller/env/cf.go
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"github.com/michaelquigley/cf"
|
||||
)
|
||||
|
||||
var cfOpts *cf.Options
|
||||
|
||||
func GetCfOptions() *cf.Options {
|
||||
if cfOpts == nil {
|
||||
cfOpts = cf.DefaultOptions()
|
||||
}
|
||||
return cfOpts
|
||||
}
|
@ -25,7 +25,7 @@ func (h *environmentDetailHandler) Handle(params metadata.GetEnvironmentDetailPa
|
||||
logrus.Errorf("environment '%v' not found for account '%v': %v", params.EnvZID, principal.Email, err)
|
||||
return metadata.NewGetEnvironmentDetailNotFound()
|
||||
}
|
||||
es := &rest_model_zrok.EnvironmentShares{
|
||||
es := &rest_model_zrok.EnvironmentAndResources{
|
||||
Environment: &rest_model_zrok.Environment{
|
||||
Address: senv.Address,
|
||||
CreatedAt: senv.CreatedAt.UnixMilli(),
|
||||
@ -40,12 +40,15 @@ func (h *environmentDetailHandler) Handle(params metadata.GetEnvironmentDetailPa
|
||||
logrus.Errorf("error finding shares for environment '%v' for user '%v': %v", senv.ZId, principal.Email, err)
|
||||
return metadata.NewGetEnvironmentDetailInternalServerError()
|
||||
}
|
||||
var sparkData map[string][]int64
|
||||
if cfg.Influx != nil {
|
||||
sparkData, err = sparkDataForShares(shrs)
|
||||
sparkRx := make(map[string][]int64)
|
||||
sparkTx := make(map[string][]int64)
|
||||
if cfg.Metrics != nil && cfg.Metrics.Influx != nil {
|
||||
sparkRx, sparkTx, err = sparkDataForShares(shrs)
|
||||
if err != nil {
|
||||
logrus.Errorf("error querying spark data for shares for user '%v': %v", principal.Email, err)
|
||||
}
|
||||
} else {
|
||||
logrus.Debug("skipping spark data for shares; no influx configuration")
|
||||
}
|
||||
for _, shr := range shrs {
|
||||
feEndpoint := ""
|
||||
@ -60,6 +63,10 @@ func (h *environmentDetailHandler) Handle(params metadata.GetEnvironmentDetailPa
|
||||
if shr.BackendProxyEndpoint != nil {
|
||||
beProxyEndpoint = *shr.BackendProxyEndpoint
|
||||
}
|
||||
var sparkData []*rest_model_zrok.SparkDataSample
|
||||
for i := 0; i < len(sparkRx[shr.Token]) && i < len(sparkTx[shr.Token]); i++ {
|
||||
sparkData = append(sparkData, &rest_model_zrok.SparkDataSample{Rx: float64(sparkRx[shr.Token][i]), Tx: float64(sparkTx[shr.Token][i])})
|
||||
}
|
||||
es.Shares = append(es.Shares, &rest_model_zrok.Share{
|
||||
Token: shr.Token,
|
||||
ZID: shr.ZId,
|
||||
@ -69,7 +76,7 @@ func (h *environmentDetailHandler) Handle(params metadata.GetEnvironmentDetailPa
|
||||
FrontendEndpoint: feEndpoint,
|
||||
BackendProxyEndpoint: beProxyEndpoint,
|
||||
Reserved: shr.Reserved,
|
||||
Metrics: sparkData[shr.Token],
|
||||
Activity: sparkData,
|
||||
CreatedAt: shr.CreatedAt.UnixMilli(),
|
||||
UpdatedAt: shr.UpdatedAt.UnixMilli(),
|
||||
})
|
||||
|
63
controller/frontendDetail.go
Normal file
63
controller/frontendDetail.go
Normal file
@ -0,0 +1,63 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/metadata"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type getFrontendDetailHandler struct{}
|
||||
|
||||
func newGetFrontendDetailHandler() *getFrontendDetailHandler {
|
||||
return &getFrontendDetailHandler{}
|
||||
}
|
||||
|
||||
func (h *getFrontendDetailHandler) Handle(params metadata.GetFrontendDetailParams, principal *rest_model_zrok.Principal) middleware.Responder {
|
||||
trx, err := str.Begin()
|
||||
if err != nil {
|
||||
logrus.Errorf("error starting transaction: %v", err)
|
||||
return metadata.NewGetFrontendDetailInternalServerError()
|
||||
}
|
||||
defer func() { _ = trx.Rollback() }()
|
||||
fe, err := str.GetFrontend(int(params.FeID), trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding share '%d': %v", params.FeID, err)
|
||||
return metadata.NewGetFrontendDetailNotFound()
|
||||
}
|
||||
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding environments for account '%v': %v", principal.Email, err)
|
||||
return metadata.NewGetFrontendDetailInternalServerError()
|
||||
}
|
||||
found := false
|
||||
if fe.EnvironmentId == nil {
|
||||
logrus.Errorf("non owned environment '%d' for '%v'", fe.Id, principal.Email)
|
||||
return metadata.NewGetFrontendDetailNotFound()
|
||||
}
|
||||
for _, env := range envs {
|
||||
if *fe.EnvironmentId == env.Id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
logrus.Errorf("environment not matched for frontend '%d' for account '%v'", fe.Id, principal.Email)
|
||||
return metadata.NewGetFrontendDetailNotFound()
|
||||
}
|
||||
payload := &rest_model_zrok.Frontend{
|
||||
ID: int64(fe.Id),
|
||||
ZID: fe.ZId,
|
||||
CreatedAt: fe.CreatedAt.UnixMilli(),
|
||||
UpdatedAt: fe.UpdatedAt.UnixMilli(),
|
||||
}
|
||||
if fe.PrivateShareId != nil {
|
||||
shr, err := str.GetShare(*fe.PrivateShareId, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error getting share for frontend '%d': %v", fe.Id, err)
|
||||
return metadata.NewGetFrontendDetailInternalServerError()
|
||||
}
|
||||
payload.ShrToken = shr.Token
|
||||
}
|
||||
return metadata.NewGetFrontendDetailOK().WithPayload(payload)
|
||||
}
|
@ -3,11 +3,12 @@ package controller
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/openziti/edge/rest_management_api_client"
|
||||
"github.com/openziti/edge/rest_management_api_client/config"
|
||||
"github.com/openziti/edge/rest_management_api_client/service"
|
||||
"github.com/openziti/edge/rest_management_api_client/service_edge_router_policy"
|
||||
"github.com/openziti/edge/rest_management_api_client/service_policy"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
"github.com/openziti/edge-api/rest_management_api_client/config"
|
||||
"github.com/openziti/edge-api/rest_management_api_client/service"
|
||||
"github.com/openziti/edge-api/rest_management_api_client/service_edge_router_policy"
|
||||
"github.com/openziti/edge-api/rest_management_api_client/service_policy"
|
||||
zrok_config "github.com/openziti/zrok/controller/config"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/pkg/errors"
|
||||
@ -16,7 +17,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GC(inCfg *Config) error {
|
||||
func GC(inCfg *zrok_config.Config) error {
|
||||
cfg = inCfg
|
||||
if v, err := store.Open(cfg.Store); err == nil {
|
||||
str = v
|
||||
@ -28,7 +29,7 @@ func GC(inCfg *Config) error {
|
||||
logrus.Errorf("error closing store: %v", err)
|
||||
}
|
||||
}()
|
||||
edge, err := edgeClient()
|
||||
edge, err := zrokEdgeSdk.Client(cfg.Ziti)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -37,7 +38,7 @@ func GC(inCfg *Config) error {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
sshrs, err := str.GetAllShares(tx)
|
||||
sshrs, err := str.FindAllShares(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -75,10 +76,10 @@ func gcServices(edge *rest_management_api_client.ZitiEdgeManagement, liveMap map
|
||||
if err := zrokEdgeSdk.DeleteServiceEdgeRouterPolicy("gc", *svc.Name, edge); err != nil {
|
||||
logrus.Errorf("error garbage collecting service edge router policy: %v", err)
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteServicePolicyDial("gc", *svc.Name, edge); err != nil {
|
||||
if err := zrokEdgeSdk.DeleteServicePoliciesDial("gc", *svc.Name, edge); err != nil {
|
||||
logrus.Errorf("error garbage collecting service dial policy: %v", err)
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteServicePolicyBind("gc", *svc.Name, edge); err != nil {
|
||||
if err := zrokEdgeSdk.DeleteServicePoliciesBind("gc", *svc.Name, edge); err != nil {
|
||||
logrus.Errorf("error garbage collecting service bind policy: %v", err)
|
||||
}
|
||||
if err := zrokEdgeSdk.DeleteConfig("gc", *svc.Name, edge); err != nil {
|
||||
@ -136,7 +137,7 @@ func gcServicePolicies(edge *rest_management_api_client.ZitiEdgeManagement, live
|
||||
if _, found := liveMap[spName]; !found {
|
||||
logrus.Infof("garbage collecting, svcId='%v'", spName)
|
||||
deleteFilter := fmt.Sprintf("id=\"%v\"", *sp.ID)
|
||||
if err := zrokEdgeSdk.DeleteServicePolicy("gc", deleteFilter, edge); err != nil {
|
||||
if err := zrokEdgeSdk.DeleteServicePolicies("gc", deleteFilter, edge); err != nil {
|
||||
logrus.Errorf("error garbage collecting service policy: %v", err)
|
||||
}
|
||||
} else {
|
||||
|
@ -2,24 +2,28 @@ package controller
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/account"
|
||||
"github.com/openziti/zrok/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type inviteHandler struct {
|
||||
cfg *Config
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func newInviteHandler(cfg *Config) *inviteHandler {
|
||||
func newInviteHandler(cfg *config.Config) *inviteHandler {
|
||||
return &inviteHandler{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *inviteHandler) Handle(params account.InviteParams) middleware.Responder {
|
||||
func (h *inviteHandler) Handle(params account.InviteParams) middleware.Responder {
|
||||
if h.cfg.Invites == nil || !h.cfg.Invites.InvitesOpen {
|
||||
logrus.Warnf("not accepting invites; attempt from '%v'", params.Body.Email)
|
||||
return account.NewInviteBadRequest()
|
||||
}
|
||||
if params.Body == nil || params.Body.Email == "" {
|
||||
logrus.Errorf("missing email")
|
||||
return account.NewInviteBadRequest()
|
||||
@ -38,11 +42,11 @@ func (self *inviteHandler) Handle(params account.InviteParams) middleware.Respon
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
if self.cfg.Registration != nil && self.cfg.Registration.TokenStrategy == "store" {
|
||||
inviteToken, err := str.GetInviteTokenByToken(params.Body.Token, tx)
|
||||
if h.cfg.Invites != nil && h.cfg.Invites.TokenStrategy == "store" {
|
||||
inviteToken, err := str.FindInviteTokenByToken(params.Body.Token, tx)
|
||||
if err != nil {
|
||||
logrus.Errorf("cannot get invite token '%v' for '%v': %v", params.Body.Token, params.Body.Email, err)
|
||||
return account.NewInviteBadRequest().WithPayload(rest_model_zrok.ErrorMessage("Missing invite token"))
|
||||
return account.NewInviteBadRequest().WithPayload("missing invite token")
|
||||
}
|
||||
if err := str.DeleteInviteToken(inviteToken.Id, tx); err != nil {
|
||||
logrus.Error(err)
|
||||
@ -62,9 +66,10 @@ func (self *inviteHandler) Handle(params account.InviteParams) middleware.Respon
|
||||
SourceAddress: params.HTTPRequest.RemoteAddr,
|
||||
}
|
||||
|
||||
if _, err := str.FindAccountWithEmail(params.Body.Email, tx); err == nil {
|
||||
// deleted accounts still exist as far as invites are concerned (ignore deleted flag)
|
||||
if _, err := str.FindAccountWithEmailAndDeleted(params.Body.Email, tx); err == nil {
|
||||
logrus.Errorf("found account for '%v', cannot process account request", params.Body.Email)
|
||||
return account.NewInviteBadRequest().WithPayload(rest_model_zrok.ErrorMessage("Duplicate email found"))
|
||||
return account.NewInviteBadRequest().WithPayload("duplicate email found")
|
||||
} else {
|
||||
logrus.Infof("no account found for '%v': %v", params.Body.Email, err)
|
||||
}
|
||||
|
48
controller/limits/accountLimitAction.go
Normal file
48
controller/limits/accountLimitAction.go
Normal file
@ -0,0 +1,48 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type accountLimitAction struct {
|
||||
str *store.Store
|
||||
zCfg *zrokEdgeSdk.Config
|
||||
}
|
||||
|
||||
func newAccountLimitAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *accountLimitAction {
|
||||
return &accountLimitAction{str, zCfg}
|
||||
}
|
||||
|
||||
func (a *accountLimitAction) HandleAccount(acct *store.Account, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error {
|
||||
logrus.Infof("limiting '%v'", acct.Email)
|
||||
|
||||
envs, err := a.str.FindEnvironmentsForAccount(acct.Id, trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error finding environments for account '%v'", acct.Email)
|
||||
}
|
||||
|
||||
edge, err := zrokEdgeSdk.Client(a.zCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, env := range envs {
|
||||
shrs, err := a.str.FindSharesForEnvironment(env.Id, trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error finding shares for environment '%v'", env.ZId)
|
||||
}
|
||||
|
||||
for _, shr := range shrs {
|
||||
if err := zrokEdgeSdk.DeleteServicePoliciesDial(env.ZId, shr.Token, edge); err != nil {
|
||||
return errors.Wrapf(err, "error deleting dial service policy for '%v'", shr.Token)
|
||||
}
|
||||
logrus.Infof("removed dial service policy for share '%v' of environment '%v'", shr.Token, env.ZId)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
54
controller/limits/accountRelaxAction.go
Normal file
54
controller/limits/accountRelaxAction.go
Normal file
@ -0,0 +1,54 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type accountRelaxAction struct {
|
||||
str *store.Store
|
||||
zCfg *zrokEdgeSdk.Config
|
||||
}
|
||||
|
||||
func newAccountRelaxAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *accountRelaxAction {
|
||||
return &accountRelaxAction{str, zCfg}
|
||||
}
|
||||
|
||||
func (a *accountRelaxAction) HandleAccount(acct *store.Account, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error {
|
||||
logrus.Infof("relaxing '%v'", acct.Email)
|
||||
|
||||
envs, err := a.str.FindEnvironmentsForAccount(acct.Id, trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error finding environments for account '%v'", acct.Email)
|
||||
}
|
||||
|
||||
edge, err := zrokEdgeSdk.Client(a.zCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, env := range envs {
|
||||
shrs, err := a.str.FindSharesForEnvironment(env.Id, trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error finding shares for environment '%v'", env.ZId)
|
||||
}
|
||||
|
||||
for _, shr := range shrs {
|
||||
switch shr.ShareMode {
|
||||
case "public":
|
||||
if err := relaxPublicShare(a.str, edge, shr, trx); err != nil {
|
||||
return errors.Wrap(err, "error relaxing public share")
|
||||
}
|
||||
case "private":
|
||||
if err := relaxPrivateShare(a.str, edge, shr, trx); err != nil {
|
||||
return errors.Wrap(err, "error relaxing private share")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
51
controller/limits/accountWarningAction.go
Normal file
51
controller/limits/accountWarningAction.go
Normal file
@ -0,0 +1,51 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/emailUi"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type accountWarningAction struct {
|
||||
str *store.Store
|
||||
cfg *emailUi.Config
|
||||
}
|
||||
|
||||
func newAccountWarningAction(cfg *emailUi.Config, str *store.Store) *accountWarningAction {
|
||||
return &accountWarningAction{str, cfg}
|
||||
}
|
||||
|
||||
func (a *accountWarningAction) HandleAccount(acct *store.Account, rxBytes, txBytes int64, limit *BandwidthPerPeriod, _ *sqlx.Tx) error {
|
||||
logrus.Infof("warning '%v'", acct.Email)
|
||||
|
||||
if a.cfg != nil {
|
||||
rxLimit := "(unlimited bytes)"
|
||||
if limit.Limit.Rx != Unlimited {
|
||||
rxLimit = util.BytesToSize(limit.Limit.Rx)
|
||||
}
|
||||
txLimit := "(unlimited bytes)"
|
||||
if limit.Limit.Tx != Unlimited {
|
||||
txLimit = util.BytesToSize(limit.Limit.Tx)
|
||||
}
|
||||
totalLimit := "(unlimited bytes)"
|
||||
if limit.Limit.Total != Unlimited {
|
||||
totalLimit = util.BytesToSize(limit.Limit.Total)
|
||||
}
|
||||
|
||||
detail := newDetailMessage()
|
||||
detail = detail.append("Your account has received %v and sent %v (for a total of %v), which has triggered a transfer limit warning.", util.BytesToSize(rxBytes), util.BytesToSize(txBytes), util.BytesToSize(rxBytes+txBytes))
|
||||
detail = detail.append("This zrok instance only allows an account to receive %v, send %v, totalling not more than %v for each %v.", rxLimit, txLimit, totalLimit, limit.Period)
|
||||
detail = detail.append("If you exceed the transfer limit, access to your shares will be temporarily disabled (until the last %v falls below the transfer limit)", limit.Period)
|
||||
|
||||
if err := sendLimitWarningEmail(a.cfg, acct.Email, detail); err != nil {
|
||||
return errors.Wrapf(err, "error sending limit warning email to '%v'", acct.Email)
|
||||
}
|
||||
} else {
|
||||
logrus.Warnf("skipping warning email for account limit; no email configuration specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
732
controller/limits/agent.go
Normal file
732
controller/limits/agent.go
Normal file
@ -0,0 +1,732 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/emailUi"
|
||||
"github.com/openziti/zrok/controller/metrics"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/openziti/zrok/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Agent struct {
|
||||
cfg *Config
|
||||
ifx *influxReader
|
||||
zCfg *zrokEdgeSdk.Config
|
||||
str *store.Store
|
||||
queue chan *metrics.Usage
|
||||
acctWarningActions []AccountAction
|
||||
acctLimitActions []AccountAction
|
||||
acctRelaxActions []AccountAction
|
||||
envWarningActions []EnvironmentAction
|
||||
envLimitActions []EnvironmentAction
|
||||
envRelaxActions []EnvironmentAction
|
||||
shrWarningActions []ShareAction
|
||||
shrLimitActions []ShareAction
|
||||
shrRelaxActions []ShareAction
|
||||
close chan struct{}
|
||||
join chan struct{}
|
||||
}
|
||||
|
||||
func NewAgent(cfg *Config, ifxCfg *metrics.InfluxConfig, zCfg *zrokEdgeSdk.Config, emailCfg *emailUi.Config, str *store.Store) (*Agent, error) {
|
||||
a := &Agent{
|
||||
cfg: cfg,
|
||||
ifx: newInfluxReader(ifxCfg),
|
||||
zCfg: zCfg,
|
||||
str: str,
|
||||
queue: make(chan *metrics.Usage, 1024),
|
||||
acctWarningActions: []AccountAction{newAccountWarningAction(emailCfg, str)},
|
||||
acctLimitActions: []AccountAction{newAccountLimitAction(str, zCfg)},
|
||||
acctRelaxActions: []AccountAction{newAccountRelaxAction(str, zCfg)},
|
||||
envWarningActions: []EnvironmentAction{newEnvironmentWarningAction(emailCfg, str)},
|
||||
envLimitActions: []EnvironmentAction{newEnvironmentLimitAction(str, zCfg)},
|
||||
envRelaxActions: []EnvironmentAction{newEnvironmentRelaxAction(str, zCfg)},
|
||||
shrWarningActions: []ShareAction{newShareWarningAction(emailCfg, str)},
|
||||
shrLimitActions: []ShareAction{newShareLimitAction(str, zCfg)},
|
||||
shrRelaxActions: []ShareAction{newShareRelaxAction(str, zCfg)},
|
||||
close: make(chan struct{}),
|
||||
join: make(chan struct{}),
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *Agent) Start() {
|
||||
go a.run()
|
||||
}
|
||||
|
||||
func (a *Agent) Stop() {
|
||||
close(a.close)
|
||||
<-a.join
|
||||
}
|
||||
|
||||
func (a *Agent) CanCreateEnvironment(acctId int, trx *sqlx.Tx) (bool, error) {
|
||||
if a.cfg.Enforcing {
|
||||
if empty, err := a.str.IsAccountLimitJournalEmpty(acctId, trx); err == nil && !empty {
|
||||
alj, err := a.str.FindLatestAccountLimitJournal(acctId, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if alj.Action == store.LimitAction {
|
||||
return false, nil
|
||||
}
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if a.cfg.Environments > Unlimited {
|
||||
envs, err := a.str.FindEnvironmentsForAccount(acctId, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(envs)+1 > a.cfg.Environments {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *Agent) CanCreateShare(acctId, envId int, trx *sqlx.Tx) (bool, error) {
|
||||
if a.cfg.Enforcing {
|
||||
if empty, err := a.str.IsAccountLimitJournalEmpty(acctId, trx); err == nil && !empty {
|
||||
alj, err := a.str.FindLatestAccountLimitJournal(acctId, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if alj.Action == store.LimitAction {
|
||||
return false, nil
|
||||
}
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if empty, err := a.str.IsEnvironmentLimitJournalEmpty(envId, trx); err == nil && !empty {
|
||||
elj, err := a.str.FindLatestEnvironmentLimitJournal(envId, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if elj.Action == store.LimitAction {
|
||||
return false, nil
|
||||
}
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if a.cfg.Shares > Unlimited {
|
||||
envs, err := a.str.FindEnvironmentsForAccount(acctId, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
total := 0
|
||||
for i := range envs {
|
||||
shrs, err := a.str.FindSharesForEnvironment(envs[i].Id, trx)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "unable to find shares for environment '%v'", envs[i].ZId)
|
||||
}
|
||||
total += len(shrs)
|
||||
if total+1 > a.cfg.Shares {
|
||||
return false, nil
|
||||
}
|
||||
logrus.Infof("total = %d", total)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) {
|
||||
if a.cfg.Enforcing {
|
||||
shr, err := a.str.GetShare(shrId, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if empty, err := a.str.IsShareLimitJournalEmpty(shr.Id, trx); err == nil && !empty {
|
||||
slj, err := a.str.FindLatestShareLimitJournal(shr.Id, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if slj.Action == store.LimitAction {
|
||||
return false, nil
|
||||
}
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
env, err := a.str.GetEnvironment(shr.EnvironmentId, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if empty, err := a.str.IsEnvironmentLimitJournalEmpty(env.Id, trx); err == nil && !empty {
|
||||
elj, err := a.str.FindLatestEnvironmentLimitJournal(env.Id, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if elj.Action == store.LimitAction {
|
||||
return false, nil
|
||||
}
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if env.AccountId != nil {
|
||||
acct, err := a.str.GetAccount(*env.AccountId, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if empty, err := a.str.IsAccountLimitJournalEmpty(acct.Id, trx); err == nil && !empty {
|
||||
alj, err := a.str.FindLatestAccountLimitJournal(acct.Id, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if alj.Action == store.LimitAction {
|
||||
return false, nil
|
||||
}
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (a *Agent) Handle(u *metrics.Usage) error {
|
||||
logrus.Debugf("handling: %v", u)
|
||||
a.queue <- u
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) run() {
|
||||
logrus.Info("started")
|
||||
defer logrus.Info("stopped")
|
||||
|
||||
lastCycle := time.Now()
|
||||
mainLoop:
|
||||
for {
|
||||
select {
|
||||
case usage := <-a.queue:
|
||||
if usage.ShareToken != "" {
|
||||
if err := a.enforce(usage); err != nil {
|
||||
logrus.Errorf("error running enforcement: %v", err)
|
||||
}
|
||||
if time.Since(lastCycle) > a.cfg.Cycle {
|
||||
if err := a.relax(); err != nil {
|
||||
logrus.Errorf("error running relax cycle: %v", err)
|
||||
}
|
||||
lastCycle = time.Now()
|
||||
}
|
||||
} else {
|
||||
logrus.Warnf("not enforcing for usage with no share token: %v", usage.String())
|
||||
}
|
||||
|
||||
case <-time.After(a.cfg.Cycle):
|
||||
if err := a.relax(); err != nil {
|
||||
logrus.Errorf("error running relax cycle: %v", err)
|
||||
}
|
||||
lastCycle = time.Now()
|
||||
|
||||
case <-a.close:
|
||||
close(a.join)
|
||||
break mainLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Agent) enforce(u *metrics.Usage) error {
|
||||
trx, err := a.str.Begin()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error starting transaction")
|
||||
}
|
||||
defer func() { _ = trx.Rollback() }()
|
||||
|
||||
acct, err := a.str.GetAccount(int(u.AccountId), trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if acct.Limitless {
|
||||
return nil
|
||||
}
|
||||
|
||||
if enforce, warning, rxBytes, txBytes, err := a.checkAccountLimit(u.AccountId); err == nil {
|
||||
if enforce {
|
||||
enforced := false
|
||||
var enforcedAt time.Time
|
||||
if empty, err := a.str.IsAccountLimitJournalEmpty(int(u.AccountId), trx); err == nil && !empty {
|
||||
if latest, err := a.str.FindLatestAccountLimitJournal(int(u.AccountId), trx); err == nil {
|
||||
enforced = latest.Action == store.LimitAction
|
||||
enforcedAt = latest.UpdatedAt
|
||||
}
|
||||
}
|
||||
|
||||
if !enforced {
|
||||
_, err := a.str.CreateAccountLimitJournal(&store.AccountLimitJournal{
|
||||
AccountId: int(u.AccountId),
|
||||
RxBytes: rxBytes,
|
||||
TxBytes: txBytes,
|
||||
Action: store.LimitAction,
|
||||
}, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acct, err := a.str.GetAccount(int(u.AccountId), trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// run account limit actions
|
||||
for _, action := range a.acctLimitActions {
|
||||
if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth.PerAccount, trx); err != nil {
|
||||
return errors.Wrapf(err, "%v", reflect.TypeOf(action).String())
|
||||
}
|
||||
}
|
||||
if err := trx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("already enforced limit for account '#%d' at %v", u.AccountId, enforcedAt)
|
||||
}
|
||||
|
||||
} else if warning {
|
||||
warned := false
|
||||
var warnedAt time.Time
|
||||
if empty, err := a.str.IsAccountLimitJournalEmpty(int(u.AccountId), trx); err == nil && !empty {
|
||||
if latest, err := a.str.FindLatestAccountLimitJournal(int(u.AccountId), trx); err == nil {
|
||||
warned = latest.Action == store.WarningAction || latest.Action == store.LimitAction
|
||||
warnedAt = latest.UpdatedAt
|
||||
}
|
||||
}
|
||||
|
||||
if !warned {
|
||||
_, err := a.str.CreateAccountLimitJournal(&store.AccountLimitJournal{
|
||||
AccountId: int(u.AccountId),
|
||||
RxBytes: rxBytes,
|
||||
TxBytes: txBytes,
|
||||
Action: store.WarningAction,
|
||||
}, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
acct, err := a.str.GetAccount(int(u.AccountId), trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// run account warning actions
|
||||
for _, action := range a.acctWarningActions {
|
||||
if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth.PerAccount, trx); err != nil {
|
||||
return errors.Wrapf(err, "%v", reflect.TypeOf(action).String())
|
||||
}
|
||||
}
|
||||
if err := trx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("already warned account '#%d' at %v", u.AccountId, warnedAt)
|
||||
}
|
||||
|
||||
} else {
|
||||
if enforce, warning, rxBytes, txBytes, err := a.checkEnvironmentLimit(u.EnvironmentId); err == nil {
|
||||
if enforce {
|
||||
enforced := false
|
||||
var enforcedAt time.Time
|
||||
if empty, err := a.str.IsEnvironmentLimitJournalEmpty(int(u.EnvironmentId), trx); err == nil && !empty {
|
||||
if latest, err := a.str.FindLatestEnvironmentLimitJournal(int(u.EnvironmentId), trx); err == nil {
|
||||
enforced = latest.Action == store.LimitAction
|
||||
enforcedAt = latest.UpdatedAt
|
||||
}
|
||||
}
|
||||
|
||||
if !enforced {
|
||||
_, err := a.str.CreateEnvironmentLimitJournal(&store.EnvironmentLimitJournal{
|
||||
EnvironmentId: int(u.EnvironmentId),
|
||||
RxBytes: rxBytes,
|
||||
TxBytes: txBytes,
|
||||
Action: store.LimitAction,
|
||||
}, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := a.str.GetEnvironment(int(u.EnvironmentId), trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// run environment limit actions
|
||||
for _, action := range a.envLimitActions {
|
||||
if err := action.HandleEnvironment(env, rxBytes, txBytes, a.cfg.Bandwidth.PerEnvironment, trx); err != nil {
|
||||
return errors.Wrapf(err, "%v", reflect.TypeOf(action).String())
|
||||
}
|
||||
}
|
||||
if err := trx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("already enforced limit for environment '#%d' at %v", u.EnvironmentId, enforcedAt)
|
||||
}
|
||||
|
||||
} else if warning {
|
||||
warned := false
|
||||
var warnedAt time.Time
|
||||
if empty, err := a.str.IsEnvironmentLimitJournalEmpty(int(u.EnvironmentId), trx); err == nil && !empty {
|
||||
if latest, err := a.str.FindLatestEnvironmentLimitJournal(int(u.EnvironmentId), trx); err == nil {
|
||||
warned = latest.Action == store.WarningAction || latest.Action == store.LimitAction
|
||||
warnedAt = latest.UpdatedAt
|
||||
}
|
||||
}
|
||||
|
||||
if !warned {
|
||||
_, err := a.str.CreateEnvironmentLimitJournal(&store.EnvironmentLimitJournal{
|
||||
EnvironmentId: int(u.EnvironmentId),
|
||||
RxBytes: rxBytes,
|
||||
TxBytes: txBytes,
|
||||
Action: store.WarningAction,
|
||||
}, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := a.str.GetEnvironment(int(u.EnvironmentId), trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// run environment warning actions
|
||||
for _, action := range a.envWarningActions {
|
||||
if err := action.HandleEnvironment(env, rxBytes, txBytes, a.cfg.Bandwidth.PerEnvironment, trx); err != nil {
|
||||
return errors.Wrapf(err, "%v", reflect.TypeOf(action).String())
|
||||
}
|
||||
}
|
||||
if err := trx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("already warned environment '#%d' at %v", u.EnvironmentId, warnedAt)
|
||||
}
|
||||
|
||||
} else {
|
||||
if enforce, warning, rxBytes, txBytes, err := a.checkShareLimit(u.ShareToken); err == nil {
|
||||
if enforce {
|
||||
shr, err := a.str.FindShareWithToken(u.ShareToken, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enforced := false
|
||||
var enforcedAt time.Time
|
||||
if empty, err := a.str.IsShareLimitJournalEmpty(shr.Id, trx); err == nil && !empty {
|
||||
if latest, err := a.str.FindLatestShareLimitJournal(shr.Id, trx); err == nil {
|
||||
enforced = latest.Action == store.LimitAction
|
||||
enforcedAt = latest.UpdatedAt
|
||||
}
|
||||
}
|
||||
|
||||
if !enforced {
|
||||
_, err := a.str.CreateShareLimitJournal(&store.ShareLimitJournal{
|
||||
ShareId: shr.Id,
|
||||
RxBytes: rxBytes,
|
||||
TxBytes: txBytes,
|
||||
Action: store.LimitAction,
|
||||
}, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// run share limit actions
|
||||
for _, action := range a.shrLimitActions {
|
||||
if err := action.HandleShare(shr, rxBytes, txBytes, a.cfg.Bandwidth.PerShare, trx); err != nil {
|
||||
return errors.Wrapf(err, "%v", reflect.TypeOf(action).String())
|
||||
}
|
||||
}
|
||||
if err := trx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("already enforced limit for share '%v' at %v", shr.Token, enforcedAt)
|
||||
}
|
||||
|
||||
} else if warning {
|
||||
shr, err := a.str.FindShareWithToken(u.ShareToken, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
warned := false
|
||||
var warnedAt time.Time
|
||||
if empty, err := a.str.IsShareLimitJournalEmpty(shr.Id, trx); err == nil && !empty {
|
||||
if latest, err := a.str.FindLatestShareLimitJournal(shr.Id, trx); err == nil {
|
||||
warned = latest.Action == store.WarningAction || latest.Action == store.LimitAction
|
||||
warnedAt = latest.UpdatedAt
|
||||
}
|
||||
}
|
||||
|
||||
if !warned {
|
||||
_, err := a.str.CreateShareLimitJournal(&store.ShareLimitJournal{
|
||||
ShareId: shr.Id,
|
||||
RxBytes: rxBytes,
|
||||
TxBytes: txBytes,
|
||||
Action: store.WarningAction,
|
||||
}, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// run share warning actions
|
||||
for _, action := range a.shrWarningActions {
|
||||
if err := action.HandleShare(shr, rxBytes, txBytes, a.cfg.Bandwidth.PerShare, trx); err != nil {
|
||||
return errors.Wrapf(err, "%v", reflect.TypeOf(action).String())
|
||||
}
|
||||
}
|
||||
if err := trx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("already warned share '%v' at %v", shr.Token, warnedAt)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) relax() error {
|
||||
logrus.Debug("relaxing")
|
||||
|
||||
trx, err := a.str.Begin()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error starting transaction")
|
||||
}
|
||||
defer func() { _ = trx.Rollback() }()
|
||||
|
||||
commit := false
|
||||
|
||||
if sljs, err := a.str.FindAllLatestShareLimitJournal(trx); err == nil {
|
||||
for _, slj := range sljs {
|
||||
if shr, err := a.str.GetShare(slj.ShareId, trx); err == nil {
|
||||
if slj.Action == store.WarningAction || slj.Action == store.LimitAction {
|
||||
if enforce, warning, rxBytes, txBytes, err := a.checkShareLimit(shr.Token); err == nil {
|
||||
if !enforce && !warning {
|
||||
if slj.Action == store.LimitAction {
|
||||
// run relax actions for share
|
||||
for _, action := range a.shrRelaxActions {
|
||||
if err := action.HandleShare(shr, rxBytes, txBytes, a.cfg.Bandwidth.PerShare, trx); err != nil {
|
||||
return errors.Wrapf(err, "%v", reflect.TypeOf(action).String())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Infof("relaxing warning for '%v'", shr.Token)
|
||||
}
|
||||
if err := a.str.DeleteShareLimitJournalForShare(shr.Id, trx); err == nil {
|
||||
commit = true
|
||||
} else {
|
||||
logrus.Errorf("error deleting share_limit_journal for '%v'", shr.Token)
|
||||
}
|
||||
} else {
|
||||
logrus.Infof("share '%v' still over limit", shr.Token)
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("error checking share limit for '%v': %v", shr.Token, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("error getting share for '#%d': %v", slj.ShareId, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
if eljs, err := a.str.FindAllLatestEnvironmentLimitJournal(trx); err == nil {
|
||||
for _, elj := range eljs {
|
||||
if env, err := a.str.GetEnvironment(elj.EnvironmentId, trx); err == nil {
|
||||
if elj.Action == store.WarningAction || elj.Action == store.LimitAction {
|
||||
if enforce, warning, rxBytes, txBytes, err := a.checkEnvironmentLimit(int64(elj.EnvironmentId)); err == nil {
|
||||
if !enforce && !warning {
|
||||
if elj.Action == store.LimitAction {
|
||||
// run relax actions for environment
|
||||
for _, action := range a.envRelaxActions {
|
||||
if err := action.HandleEnvironment(env, rxBytes, txBytes, a.cfg.Bandwidth.PerEnvironment, trx); err != nil {
|
||||
return errors.Wrapf(err, "%v", reflect.TypeOf(action).String())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Infof("relaxing warning for '%v'", env.ZId)
|
||||
}
|
||||
if err := a.str.DeleteEnvironmentLimitJournalForEnvironment(env.Id, trx); err == nil {
|
||||
commit = true
|
||||
} else {
|
||||
logrus.Errorf("error deleteing environment_limit_journal for '%v': %v", env.ZId, err)
|
||||
}
|
||||
} else {
|
||||
logrus.Infof("environment '%v' still over limit", env.ZId)
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("error checking environment limit for '%v': %v", env.ZId, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("error getting environment for '#%d': %v", elj.EnvironmentId, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
if aljs, err := a.str.FindAllLatestAccountLimitJournal(trx); err == nil {
|
||||
for _, alj := range aljs {
|
||||
if acct, err := a.str.GetAccount(alj.AccountId, trx); err == nil {
|
||||
if alj.Action == store.WarningAction || alj.Action == store.LimitAction {
|
||||
if enforce, warning, rxBytes, txBytes, err := a.checkAccountLimit(int64(alj.AccountId)); err == nil {
|
||||
if !enforce && !warning {
|
||||
if alj.Action == store.LimitAction {
|
||||
// run relax actions for account
|
||||
for _, action := range a.acctRelaxActions {
|
||||
if err := action.HandleAccount(acct, rxBytes, txBytes, a.cfg.Bandwidth.PerAccount, trx); err != nil {
|
||||
return errors.Wrapf(err, "%v", reflect.TypeOf(action).String())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Infof("relaxing warning for '%v'", acct.Email)
|
||||
}
|
||||
if err := a.str.DeleteAccountLimitJournalForAccount(acct.Id, trx); err == nil {
|
||||
commit = true
|
||||
} else {
|
||||
logrus.Errorf("error deleting account_limit_journal for '%v': %v", acct.Email, err)
|
||||
}
|
||||
} else {
|
||||
logrus.Infof("account '%v' still over limit", acct.Email)
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("error checking account limit for '%v': %v", acct.Email, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("error getting account for '#%d': %v", alj.AccountId, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
if commit {
|
||||
if err := trx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) checkAccountLimit(acctId int64) (enforce, warning bool, rxBytes, txBytes int64, err error) {
|
||||
period := 24 * time.Hour
|
||||
limit := DefaultBandwidthPerPeriod()
|
||||
if a.cfg.Bandwidth != nil && a.cfg.Bandwidth.PerAccount != nil {
|
||||
limit = a.cfg.Bandwidth.PerAccount
|
||||
}
|
||||
if limit.Period > 0 {
|
||||
period = limit.Period
|
||||
}
|
||||
rx, tx, err := a.ifx.totalRxTxForAccount(acctId, period)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
|
||||
enforce, warning = a.checkLimit(limit, rx, tx)
|
||||
return enforce, warning, rx, tx, nil
|
||||
}
|
||||
|
||||
func (a *Agent) checkEnvironmentLimit(envId int64) (enforce, warning bool, rxBytes, txBytes int64, err error) {
|
||||
period := 24 * time.Hour
|
||||
limit := DefaultBandwidthPerPeriod()
|
||||
if a.cfg.Bandwidth != nil && a.cfg.Bandwidth.PerEnvironment != nil {
|
||||
limit = a.cfg.Bandwidth.PerEnvironment
|
||||
}
|
||||
if limit.Period > 0 {
|
||||
period = limit.Period
|
||||
}
|
||||
rx, tx, err := a.ifx.totalRxTxForEnvironment(envId, period)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
|
||||
enforce, warning = a.checkLimit(limit, rx, tx)
|
||||
return enforce, warning, rx, tx, nil
|
||||
}
|
||||
|
||||
func (a *Agent) checkShareLimit(shrToken string) (enforce, warning bool, rxBytes, txBytes int64, err error) {
|
||||
period := 24 * time.Hour
|
||||
limit := DefaultBandwidthPerPeriod()
|
||||
if a.cfg.Bandwidth != nil && a.cfg.Bandwidth.PerShare != nil {
|
||||
limit = a.cfg.Bandwidth.PerShare
|
||||
}
|
||||
if limit.Period > 0 {
|
||||
period = limit.Period
|
||||
}
|
||||
rx, tx, err := a.ifx.totalRxTxForShare(shrToken, period)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
|
||||
enforce, warning = a.checkLimit(limit, rx, tx)
|
||||
if enforce || warning {
|
||||
logrus.Debugf("'%v': %v", shrToken, describeLimit(limit, rx, tx))
|
||||
}
|
||||
|
||||
return enforce, warning, rx, tx, nil
|
||||
}
|
||||
|
||||
func (a *Agent) checkLimit(cfg *BandwidthPerPeriod, rx, tx int64) (enforce, warning bool) {
|
||||
if cfg.Limit.Rx != Unlimited && rx > cfg.Limit.Rx {
|
||||
return true, false
|
||||
}
|
||||
if cfg.Limit.Tx != Unlimited && tx > cfg.Limit.Tx {
|
||||
return true, false
|
||||
}
|
||||
if cfg.Limit.Total != Unlimited && rx+tx > cfg.Limit.Total {
|
||||
return true, false
|
||||
}
|
||||
|
||||
if cfg.Warning.Rx != Unlimited && rx > cfg.Warning.Rx {
|
||||
return false, true
|
||||
}
|
||||
if cfg.Warning.Tx != Unlimited && tx > cfg.Warning.Tx {
|
||||
return false, true
|
||||
}
|
||||
if cfg.Warning.Total != Unlimited && rx+tx > cfg.Warning.Total {
|
||||
return false, true
|
||||
}
|
||||
|
||||
return false, false
|
||||
}
|
||||
|
||||
func describeLimit(cfg *BandwidthPerPeriod, rx, tx int64) string {
|
||||
out := ""
|
||||
|
||||
if cfg.Limit.Rx != Unlimited && rx > cfg.Limit.Rx {
|
||||
out += fmt.Sprintf("['%v' over rx limit '%v']", util.BytesToSize(rx), util.BytesToSize(cfg.Limit.Rx))
|
||||
}
|
||||
if cfg.Limit.Tx != Unlimited && tx > cfg.Limit.Tx {
|
||||
out += fmt.Sprintf("['%v' over tx limit '%v']", util.BytesToSize(tx), util.BytesToSize(cfg.Limit.Tx))
|
||||
}
|
||||
if cfg.Limit.Total != Unlimited && rx+tx > cfg.Limit.Total {
|
||||
out += fmt.Sprintf("['%v' over total limit '%v']", util.BytesToSize(rx+tx), util.BytesToSize(cfg.Limit.Total))
|
||||
}
|
||||
|
||||
if cfg.Warning.Rx != Unlimited && rx > cfg.Warning.Rx {
|
||||
out += fmt.Sprintf("['%v' over rx warning '%v']", util.BytesToSize(rx), util.BytesToSize(cfg.Warning.Rx))
|
||||
}
|
||||
if cfg.Warning.Tx != Unlimited && tx > cfg.Warning.Tx {
|
||||
out += fmt.Sprintf("['%v' over tx warning '%v']", util.BytesToSize(tx), util.BytesToSize(cfg.Warning.Tx))
|
||||
}
|
||||
if cfg.Warning.Total != Unlimited && rx+tx > cfg.Warning.Total {
|
||||
out += fmt.Sprintf("['%v' over total warning '%v']", util.BytesToSize(rx+tx), util.BytesToSize(cfg.Warning.Total))
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
61
controller/limits/config.go
Normal file
61
controller/limits/config.go
Normal file
@ -0,0 +1,61 @@
|
||||
package limits
|
||||
|
||||
import "time"
|
||||
|
||||
const Unlimited = -1
|
||||
|
||||
type Config struct {
|
||||
Environments int
|
||||
Shares int
|
||||
Bandwidth *BandwidthConfig
|
||||
Cycle time.Duration
|
||||
Enforcing bool
|
||||
}
|
||||
|
||||
type BandwidthConfig struct {
|
||||
PerAccount *BandwidthPerPeriod
|
||||
PerEnvironment *BandwidthPerPeriod
|
||||
PerShare *BandwidthPerPeriod
|
||||
}
|
||||
|
||||
type BandwidthPerPeriod struct {
|
||||
Period time.Duration
|
||||
Warning *Bandwidth
|
||||
Limit *Bandwidth
|
||||
}
|
||||
|
||||
type Bandwidth struct {
|
||||
Rx int64
|
||||
Tx int64
|
||||
Total int64
|
||||
}
|
||||
|
||||
func DefaultBandwidthPerPeriod() *BandwidthPerPeriod {
|
||||
return &BandwidthPerPeriod{
|
||||
Period: 24 * time.Hour,
|
||||
Warning: &Bandwidth{
|
||||
Rx: Unlimited,
|
||||
Tx: Unlimited,
|
||||
Total: Unlimited,
|
||||
},
|
||||
Limit: &Bandwidth{
|
||||
Rx: Unlimited,
|
||||
Tx: Unlimited,
|
||||
Total: Unlimited,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Environments: Unlimited,
|
||||
Shares: Unlimited,
|
||||
Bandwidth: &BandwidthConfig{
|
||||
PerAccount: DefaultBandwidthPerPeriod(),
|
||||
PerEnvironment: DefaultBandwidthPerPeriod(),
|
||||
PerShare: DefaultBandwidthPerPeriod(),
|
||||
},
|
||||
Enforcing: false,
|
||||
Cycle: 15 * time.Minute,
|
||||
}
|
||||
}
|
92
controller/limits/email.go
Normal file
92
controller/limits/email.go
Normal file
@ -0,0 +1,92 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/openziti/zrok/build"
|
||||
"github.com/openziti/zrok/controller/emailUi"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/wneessen/go-mail"
|
||||
)
|
||||
|
||||
type detailMessage struct {
|
||||
lines []string
|
||||
}
|
||||
|
||||
func newDetailMessage() *detailMessage {
|
||||
return &detailMessage{}
|
||||
}
|
||||
|
||||
func (m *detailMessage) append(msg string, args ...interface{}) *detailMessage {
|
||||
m.lines = append(m.lines, fmt.Sprintf(msg, args...))
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *detailMessage) html() string {
|
||||
out := ""
|
||||
for i := range m.lines {
|
||||
out += fmt.Sprintf("<p style=\"text-align: left;\">%s</p>\n", m.lines[i])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (m *detailMessage) plain() string {
|
||||
out := ""
|
||||
for i := range m.lines {
|
||||
out += fmt.Sprintf("%s\n\n", m.lines[i])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func sendLimitWarningEmail(cfg *emailUi.Config, emailTo string, d *detailMessage) error {
|
||||
emailData := &emailUi.WarningEmail{
|
||||
EmailAddress: emailTo,
|
||||
Version: build.String(),
|
||||
}
|
||||
|
||||
emailData.Detail = d.plain()
|
||||
plainBody, err := emailData.MergeTemplate("limitWarning.gotext")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
emailData.Detail = d.html()
|
||||
htmlBody, err := emailData.MergeTemplate("limitWarning.gohtml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := mail.NewMsg()
|
||||
if err := msg.From(cfg.From); err != nil {
|
||||
return errors.Wrap(err, "failed to set from address in limit warning email")
|
||||
}
|
||||
if err := msg.To(emailTo); err != nil {
|
||||
return errors.Wrap(err, "failed to set to address in limit warning email")
|
||||
}
|
||||
|
||||
msg.Subject("zrok Limit Warning Notification")
|
||||
msg.SetDate()
|
||||
msg.SetMessageID()
|
||||
msg.SetBulk()
|
||||
msg.SetImportance(mail.ImportanceHigh)
|
||||
msg.SetBodyString(mail.TypeTextPlain, plainBody)
|
||||
msg.SetBodyString(mail.TypeTextHTML, htmlBody)
|
||||
|
||||
client, err := mail.NewClient(cfg.Host,
|
||||
mail.WithPort(cfg.Port),
|
||||
mail.WithSMTPAuth(mail.SMTPAuthPlain),
|
||||
mail.WithUsername(cfg.Username),
|
||||
mail.WithPassword(cfg.Password),
|
||||
mail.WithTLSPolicy(mail.TLSMandatory),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating limit warning email client")
|
||||
}
|
||||
if err := client.DialAndSend(msg); err != nil {
|
||||
return errors.Wrap(err, "error sending limit warning email")
|
||||
}
|
||||
|
||||
logrus.Infof("limit warning email sent to '%v'", emailTo)
|
||||
return nil
|
||||
}
|
41
controller/limits/environmentLimitAction.go
Normal file
41
controller/limits/environmentLimitAction.go
Normal file
@ -0,0 +1,41 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type environmentLimitAction struct {
|
||||
str *store.Store
|
||||
zCfg *zrokEdgeSdk.Config
|
||||
}
|
||||
|
||||
func newEnvironmentLimitAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *environmentLimitAction {
|
||||
return &environmentLimitAction{str, zCfg}
|
||||
}
|
||||
|
||||
func (a *environmentLimitAction) HandleEnvironment(env *store.Environment, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error {
|
||||
logrus.Infof("limiting '%v'", env.ZId)
|
||||
|
||||
shrs, err := a.str.FindSharesForEnvironment(env.Id, trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error finding shares for environment '%v'", env.ZId)
|
||||
}
|
||||
|
||||
edge, err := zrokEdgeSdk.Client(a.zCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, shr := range shrs {
|
||||
if err := zrokEdgeSdk.DeleteServicePoliciesDial(env.ZId, shr.Token, edge); err != nil {
|
||||
return errors.Wrapf(err, "error deleting dial service policy for '%v'", shr.Token)
|
||||
}
|
||||
logrus.Infof("removed dial service policy for share '%v' of environment '%v'", shr.Token, env.ZId)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
49
controller/limits/environmentRelaxAction.go
Normal file
49
controller/limits/environmentRelaxAction.go
Normal file
@ -0,0 +1,49 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type environmentRelaxAction struct {
|
||||
str *store.Store
|
||||
zCfg *zrokEdgeSdk.Config
|
||||
}
|
||||
|
||||
func newEnvironmentRelaxAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *environmentRelaxAction {
|
||||
return &environmentRelaxAction{str, zCfg}
|
||||
}
|
||||
|
||||
func (a *environmentRelaxAction) HandleEnvironment(env *store.Environment, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error {
|
||||
logrus.Infof("relaxing '%v'", env.ZId)
|
||||
|
||||
shrs, err := a.str.FindSharesForEnvironment(env.Id, trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error finding shares for environment '%v'", env.ZId)
|
||||
}
|
||||
|
||||
edge, err := zrokEdgeSdk.Client(a.zCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, shr := range shrs {
|
||||
if !shr.Deleted {
|
||||
switch shr.ShareMode {
|
||||
case "public":
|
||||
if err := relaxPublicShare(a.str, edge, shr, trx); err != nil {
|
||||
return err
|
||||
}
|
||||
case "private":
|
||||
if err := relaxPrivateShare(a.str, edge, shr, trx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
58
controller/limits/environmentWarningAction.go
Normal file
58
controller/limits/environmentWarningAction.go
Normal file
@ -0,0 +1,58 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/emailUi"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type environmentWarningAction struct {
|
||||
str *store.Store
|
||||
cfg *emailUi.Config
|
||||
}
|
||||
|
||||
func newEnvironmentWarningAction(cfg *emailUi.Config, str *store.Store) *environmentWarningAction {
|
||||
return &environmentWarningAction{str, cfg}
|
||||
}
|
||||
|
||||
func (a *environmentWarningAction) HandleEnvironment(env *store.Environment, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error {
|
||||
logrus.Infof("warning '%v'", env.ZId)
|
||||
|
||||
if a.cfg != nil {
|
||||
if env.AccountId != nil {
|
||||
acct, err := a.str.GetAccount(*env.AccountId, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rxLimit := "unlimited bytes"
|
||||
if limit.Limit.Rx != Unlimited {
|
||||
rxLimit = util.BytesToSize(limit.Limit.Rx)
|
||||
}
|
||||
txLimit := "unlimited bytes"
|
||||
if limit.Limit.Tx != Unlimited {
|
||||
txLimit = util.BytesToSize(limit.Limit.Tx)
|
||||
}
|
||||
totalLimit := "unlimited bytes"
|
||||
if limit.Limit.Total != Unlimited {
|
||||
totalLimit = util.BytesToSize(limit.Limit.Total)
|
||||
}
|
||||
|
||||
detail := newDetailMessage()
|
||||
detail = detail.append("Your environment '%v' has received %v and sent %v (for a total of %v), which has triggered a transfer limit warning.", env.Description, util.BytesToSize(rxBytes), util.BytesToSize(txBytes), util.BytesToSize(rxBytes+txBytes))
|
||||
detail = detail.append("This zrok instance only allows a share to receive %v, send %v, totalling not more than %v for each %v.", rxLimit, txLimit, totalLimit, limit.Period)
|
||||
detail = detail.append("If you exceed the transfer limit, access to your shares will be temporarily disabled (until the last %v falls below the transfer limit).", limit.Period)
|
||||
|
||||
if err := sendLimitWarningEmail(a.cfg, acct.Email, detail); err != nil {
|
||||
return errors.Wrapf(err, "error sending limit warning email to '%v'", acct.Email)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Warnf("skipping warning email for environment limit; no email configuration specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
88
controller/limits/influxReader.go
Normal file
88
controller/limits/influxReader.go
Normal file
@ -0,0 +1,88 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
"github.com/influxdata/influxdb-client-go/v2/api"
|
||||
"github.com/openziti/zrok/controller/metrics"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type influxReader struct {
|
||||
cfg *metrics.InfluxConfig
|
||||
idb influxdb2.Client
|
||||
queryApi api.QueryAPI
|
||||
}
|
||||
|
||||
func newInfluxReader(cfg *metrics.InfluxConfig) *influxReader {
|
||||
idb := influxdb2.NewClient(cfg.Url, cfg.Token)
|
||||
queryApi := idb.QueryAPI(cfg.Org)
|
||||
return &influxReader{cfg, idb, queryApi}
|
||||
}
|
||||
|
||||
func (r *influxReader) totalRxTxForAccount(acctId int64, duration time.Duration) (int64, int64, error) {
|
||||
query := fmt.Sprintf("from(bucket: \"%v\")\n", r.cfg.Bucket) +
|
||||
fmt.Sprintf("|> range(start: -%v)\n", duration) +
|
||||
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")\n" +
|
||||
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
||||
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
||||
fmt.Sprintf("|> filter(fn: (r) => r[\"acctId\"] == \"%d\")\n", acctId) +
|
||||
"|> drop(columns: [\"share\", \"envId\"])\n" +
|
||||
"|> sum()"
|
||||
return r.runQueryForRxTx(query)
|
||||
}
|
||||
|
||||
func (r *influxReader) totalRxTxForEnvironment(envId int64, duration time.Duration) (int64, int64, error) {
|
||||
query := fmt.Sprintf("from(bucket: \"%v\")\n", r.cfg.Bucket) +
|
||||
fmt.Sprintf("|> range(start: -%v)\n", duration) +
|
||||
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")\n" +
|
||||
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
||||
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
||||
fmt.Sprintf("|> filter(fn: (r) => r[\"envId\"] == \"%d\")\n", envId) +
|
||||
"|> drop(columns: [\"share\", \"acctId\"])\n" +
|
||||
"|> sum()"
|
||||
return r.runQueryForRxTx(query)
|
||||
}
|
||||
|
||||
func (r *influxReader) totalRxTxForShare(shrToken string, duration time.Duration) (int64, int64, error) {
|
||||
query := fmt.Sprintf("from(bucket: \"%v\")\n", r.cfg.Bucket) +
|
||||
fmt.Sprintf("|> range(start: -%v)\n", duration) +
|
||||
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")\n" +
|
||||
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
||||
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
||||
fmt.Sprintf("|> filter(fn: (r) => r[\"share\"] == \"%v\")\n", shrToken) +
|
||||
"|> sum()"
|
||||
return r.runQueryForRxTx(query)
|
||||
}
|
||||
|
||||
func (r *influxReader) runQueryForRxTx(query string) (rx int64, tx int64, err error) {
|
||||
result, err := r.queryApi.Query(context.Background(), query)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
|
||||
count := 0
|
||||
for result.Next() {
|
||||
if v, ok := result.Record().Value().(int64); ok {
|
||||
switch result.Record().Field() {
|
||||
case "tx":
|
||||
tx = v
|
||||
case "rx":
|
||||
rx = v
|
||||
default:
|
||||
logrus.Warnf("field '%v'?", result.Record().Field())
|
||||
}
|
||||
} else {
|
||||
return -1, -1, errors.New("error asserting value type")
|
||||
}
|
||||
count++
|
||||
}
|
||||
if count != 0 && count != 2 {
|
||||
return -1, -1, errors.Errorf("expected 2 results; got '%d' (%v)", count, strings.ReplaceAll(query, "\n", ""))
|
||||
}
|
||||
return rx, tx, nil
|
||||
}
|
18
controller/limits/model.go
Normal file
18
controller/limits/model.go
Normal file
@ -0,0 +1,18 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
)
|
||||
|
||||
type AccountAction interface {
|
||||
HandleAccount(a *store.Account, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error
|
||||
}
|
||||
|
||||
type EnvironmentAction interface {
|
||||
HandleEnvironment(e *store.Environment, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error
|
||||
}
|
||||
|
||||
type ShareAction interface {
|
||||
HandleShare(s *store.Share, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error
|
||||
}
|
38
controller/limits/shareLimitAction.go
Normal file
38
controller/limits/shareLimitAction.go
Normal file
@ -0,0 +1,38 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type shareLimitAction struct {
|
||||
str *store.Store
|
||||
zCfg *zrokEdgeSdk.Config
|
||||
}
|
||||
|
||||
func newShareLimitAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *shareLimitAction {
|
||||
return &shareLimitAction{str, zCfg}
|
||||
}
|
||||
|
||||
func (a *shareLimitAction) HandleShare(shr *store.Share, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error {
|
||||
logrus.Infof("limiting '%v'", shr.Token)
|
||||
|
||||
env, err := a.str.GetEnvironment(shr.EnvironmentId, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
edge, err := zrokEdgeSdk.Client(a.zCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := zrokEdgeSdk.DeleteServicePoliciesDial(env.ZId, shr.Token, edge); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Infof("removed dial service policy for '%v'", shr.Token)
|
||||
|
||||
return nil
|
||||
}
|
88
controller/limits/shareRelaxAction.go
Normal file
88
controller/limits/shareRelaxAction.go
Normal file
@ -0,0 +1,88 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type shareRelaxAction struct {
|
||||
str *store.Store
|
||||
zCfg *zrokEdgeSdk.Config
|
||||
}
|
||||
|
||||
func newShareRelaxAction(str *store.Store, zCfg *zrokEdgeSdk.Config) *shareRelaxAction {
|
||||
return &shareRelaxAction{str, zCfg}
|
||||
}
|
||||
|
||||
func (a *shareRelaxAction) HandleShare(shr *store.Share, _, _ int64, _ *BandwidthPerPeriod, trx *sqlx.Tx) error {
|
||||
logrus.Infof("relaxing '%v'", shr.Token)
|
||||
|
||||
if !shr.Deleted {
|
||||
edge, err := zrokEdgeSdk.Client(a.zCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch shr.ShareMode {
|
||||
case "public":
|
||||
if err := relaxPublicShare(a.str, edge, shr, trx); err != nil {
|
||||
return err
|
||||
}
|
||||
case "private":
|
||||
if err := relaxPrivateShare(a.str, edge, shr, trx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func relaxPublicShare(str *store.Store, edge *rest_management_api_client.ZitiEdgeManagement, shr *store.Share, trx *sqlx.Tx) error {
|
||||
env, err := str.GetEnvironment(shr.EnvironmentId, trx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error finding environment")
|
||||
}
|
||||
|
||||
fe, err := str.FindFrontendPubliclyNamed(*shr.FrontendSelection, trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error finding frontend name '%v' for '%v'", *shr.FrontendSelection, shr.Token)
|
||||
}
|
||||
|
||||
if err := zrokEdgeSdk.CreateServicePolicyDial(env.ZId+"-"+shr.ZId+"-dial", shr.ZId, []string{fe.ZId}, zrokEdgeSdk.ZrokShareTags(shr.Token).SubTags, edge); err != nil {
|
||||
return errors.Wrapf(err, "error creating dial service policy for '%v'", shr.Token)
|
||||
}
|
||||
logrus.Infof("added dial service policy for '%v'", shr.Token)
|
||||
return nil
|
||||
}
|
||||
|
||||
func relaxPrivateShare(str *store.Store, edge *rest_management_api_client.ZitiEdgeManagement, shr *store.Share, trx *sqlx.Tx) error {
|
||||
fes, err := str.FindFrontendsForPrivateShare(shr.Id, trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error finding frontends for share '%v'", shr.Token)
|
||||
}
|
||||
for _, fe := range fes {
|
||||
if fe.EnvironmentId != nil {
|
||||
env, err := str.GetEnvironment(*fe.EnvironmentId, trx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error getting environment for frontend '%v'", fe.Token)
|
||||
}
|
||||
|
||||
addlTags := map[string]interface{}{
|
||||
"zrokEnvironmentZId": env.ZId,
|
||||
"zrokFrontendToken": fe.Token,
|
||||
"zrokShareToken": shr.Token,
|
||||
}
|
||||
if err := zrokEdgeSdk.CreateServicePolicyDial(fe.Token+"-"+env.ZId+"-"+shr.ZId+"-dial", shr.ZId, []string{env.ZId}, addlTags, edge); err != nil {
|
||||
return errors.Wrapf(err, "unable to create dial policy for frontend '%v'", fe.Token)
|
||||
}
|
||||
|
||||
logrus.Infof("added dial service policy for share '%v' to private frontend '%v'", shr.Token, fe.Token)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
63
controller/limits/shareWarningAction.go
Normal file
63
controller/limits/shareWarningAction.go
Normal file
@ -0,0 +1,63 @@
|
||||
package limits
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/emailUi"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type shareWarningAction struct {
|
||||
str *store.Store
|
||||
cfg *emailUi.Config
|
||||
}
|
||||
|
||||
func newShareWarningAction(cfg *emailUi.Config, str *store.Store) *shareWarningAction {
|
||||
return &shareWarningAction{str, cfg}
|
||||
}
|
||||
|
||||
func (a *shareWarningAction) HandleShare(shr *store.Share, rxBytes, txBytes int64, limit *BandwidthPerPeriod, trx *sqlx.Tx) error {
|
||||
logrus.Infof("warning '%v'", shr.Token)
|
||||
|
||||
if a.cfg != nil {
|
||||
env, err := a.str.GetEnvironment(shr.EnvironmentId, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if env.AccountId != nil {
|
||||
acct, err := a.str.GetAccount(*env.AccountId, trx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rxLimit := "unlimited bytes"
|
||||
if limit.Limit.Rx != Unlimited {
|
||||
rxLimit = util.BytesToSize(limit.Limit.Rx)
|
||||
}
|
||||
txLimit := "unlimited bytes"
|
||||
if limit.Limit.Tx != Unlimited {
|
||||
txLimit = util.BytesToSize(limit.Limit.Tx)
|
||||
}
|
||||
totalLimit := "unlimited bytes"
|
||||
if limit.Limit.Total != Unlimited {
|
||||
totalLimit = util.BytesToSize(limit.Limit.Total)
|
||||
}
|
||||
|
||||
detail := newDetailMessage()
|
||||
detail = detail.append("Your share '%v' has received %v and sent %v (for a total of %v), which has triggered a transfer limit warning.", shr.Token, util.BytesToSize(rxBytes), util.BytesToSize(txBytes), util.BytesToSize(rxBytes+txBytes))
|
||||
detail = detail.append("This zrok instance only allows a share to receive %v, send %v, totalling not more than %v for each %v.", rxLimit, txLimit, totalLimit, limit.Period)
|
||||
detail = detail.append("If you exceed the transfer limit, access to your shares will be temporarily disabled (until the last %v falls below the transfer limit).", limit.Period)
|
||||
|
||||
if err := sendLimitWarningEmail(a.cfg, acct.Email, detail); err != nil {
|
||||
return errors.Wrapf(err, "error sending limit warning email to '%v'", acct.Email)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Warnf("skipping warning email for share limit; no email configuration specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -11,11 +12,11 @@ import (
|
||||
)
|
||||
|
||||
type maintenanceRegistrationAgent struct {
|
||||
cfg *RegistrationMaintenanceConfig
|
||||
cfg *config.RegistrationMaintenanceConfig
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func newRegistrationMaintenanceAgent(ctx context.Context, cfg *RegistrationMaintenanceConfig) *maintenanceRegistrationAgent {
|
||||
func newRegistrationMaintenanceAgent(ctx context.Context, cfg *config.RegistrationMaintenanceConfig) *maintenanceRegistrationAgent {
|
||||
return &maintenanceRegistrationAgent{
|
||||
cfg: cfg,
|
||||
ctx: ctx,
|
||||
@ -78,11 +79,11 @@ func (ma *maintenanceRegistrationAgent) deleteExpiredAccountRequests() error {
|
||||
}
|
||||
|
||||
type maintenanceResetPasswordAgent struct {
|
||||
cfg *ResetPasswordMaintenanceConfig
|
||||
cfg *config.ResetPasswordMaintenanceConfig
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func newMaintenanceResetPasswordAgent(ctx context.Context, cfg *ResetPasswordMaintenanceConfig) *maintenanceResetPasswordAgent {
|
||||
func newMaintenanceResetPasswordAgent(ctx context.Context, cfg *config.ResetPasswordMaintenanceConfig) *maintenanceResetPasswordAgent {
|
||||
return &maintenanceResetPasswordAgent{
|
||||
cfg: cfg,
|
||||
ctx: ctx,
|
||||
|
@ -1,181 +1,261 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
"github.com/influxdata/influxdb-client-go/v2/api"
|
||||
"github.com/influxdata/influxdb-client-go/v2/api/write"
|
||||
"github.com/openziti/sdk-golang/ziti"
|
||||
"github.com/openziti/sdk-golang/ziti/config"
|
||||
"github.com/openziti/sdk-golang/ziti/edge"
|
||||
"github.com/openziti/zrok/model"
|
||||
"github.com/openziti/zrok/util"
|
||||
"github.com/openziti/zrok/zrokdir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/openziti/zrok/controller/metrics"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/metadata"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type metricsAgent struct {
|
||||
writeApi api.WriteAPIBlocking
|
||||
metricsQueue chan *model.Metrics
|
||||
envCache map[string]*envCacheEntry
|
||||
zCtx ziti.Context
|
||||
zListener edge.Listener
|
||||
shutdown chan struct{}
|
||||
joined chan struct{}
|
||||
type getAccountMetricsHandler struct {
|
||||
cfg *metrics.InfluxConfig
|
||||
idb influxdb2.Client
|
||||
queryApi api.QueryAPI
|
||||
}
|
||||
|
||||
type envCacheEntry struct {
|
||||
env string
|
||||
lastAccess time.Time
|
||||
}
|
||||
|
||||
func newMetricsAgent() *metricsAgent {
|
||||
ma := &metricsAgent{
|
||||
metricsQueue: make(chan *model.Metrics, 1024),
|
||||
envCache: make(map[string]*envCacheEntry),
|
||||
shutdown: make(chan struct{}),
|
||||
joined: make(chan struct{}),
|
||||
}
|
||||
if idb != nil {
|
||||
ma.writeApi = idb.WriteAPIBlocking(cfg.Influx.Org, cfg.Influx.Bucket)
|
||||
}
|
||||
return ma
|
||||
}
|
||||
|
||||
func (ma *metricsAgent) run() {
|
||||
logrus.Info("starting")
|
||||
defer logrus.Info("exiting")
|
||||
defer close(ma.joined)
|
||||
|
||||
if err := ma.bindService(); err != nil {
|
||||
logrus.Errorf("error binding metrics service: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
work:
|
||||
for {
|
||||
select {
|
||||
case <-ma.shutdown:
|
||||
break work
|
||||
|
||||
case m := <-ma.metricsQueue:
|
||||
if err := ma.processMetrics(m); err != nil {
|
||||
logrus.Errorf("error processing metrics: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ma.zListener.Close(); err != nil {
|
||||
logrus.Errorf("error closing metrics service listener: %v", err)
|
||||
func newGetAccountMetricsHandler(cfg *metrics.InfluxConfig) *getAccountMetricsHandler {
|
||||
idb := influxdb2.NewClient(cfg.Url, cfg.Token)
|
||||
queryApi := idb.QueryAPI(cfg.Org)
|
||||
return &getAccountMetricsHandler{
|
||||
cfg: cfg,
|
||||
idb: idb,
|
||||
queryApi: queryApi,
|
||||
}
|
||||
}
|
||||
|
||||
func (ma *metricsAgent) bindService() error {
|
||||
zif, err := zrokdir.ZitiIdentityFile("ctrl")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting 'ctrl' identity")
|
||||
}
|
||||
zCfg, err := config.NewFromFile(zif)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error loading 'ctrl' identity")
|
||||
}
|
||||
ma.zCtx = ziti.NewContextWithConfig(zCfg)
|
||||
opts := &ziti.ListenOptions{
|
||||
ConnectTimeout: 5 * time.Minute,
|
||||
MaxConnections: 1024,
|
||||
}
|
||||
ma.zListener, err = ma.zCtx.ListenWithOptions(cfg.Metrics.ServiceName, opts)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error listening for metrics on '%v'", cfg.Metrics.ServiceName)
|
||||
}
|
||||
go ma.listen()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ma *metricsAgent) listen() {
|
||||
logrus.Info("started")
|
||||
defer logrus.Info("exited")
|
||||
for {
|
||||
conn, err := ma.zListener.Accept()
|
||||
func (h *getAccountMetricsHandler) Handle(params metadata.GetAccountMetricsParams, principal *rest_model_zrok.Principal) middleware.Responder {
|
||||
duration := 30 * 24 * time.Hour
|
||||
if params.Duration != nil {
|
||||
v, err := time.ParseDuration(*params.Duration)
|
||||
if err != nil {
|
||||
logrus.Errorf("error accepting: %v", err)
|
||||
return
|
||||
logrus.Errorf("bad duration '%v' for '%v': %v", *params.Duration, principal.Email, err)
|
||||
return metadata.NewGetAccountMetricsBadRequest()
|
||||
}
|
||||
logrus.Debugf("accepted metrics connetion from '%v'", conn.RemoteAddr())
|
||||
go newMetricsHandler(conn, ma.metricsQueue).run()
|
||||
duration = v
|
||||
}
|
||||
slice := sliceSize(duration)
|
||||
|
||||
query := fmt.Sprintf("from(bucket: \"%v\")\n", h.cfg.Bucket) +
|
||||
fmt.Sprintf("|> range(start: -%v)\n", duration) +
|
||||
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")\n" +
|
||||
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
||||
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
||||
fmt.Sprintf("|> filter(fn: (r) => r[\"acctId\"] == \"%d\")\n", principal.ID) +
|
||||
"|> drop(columns: [\"share\", \"envId\"])\n" +
|
||||
fmt.Sprintf("|> aggregateWindow(every: %v, fn: sum, createEmpty: true)", slice)
|
||||
|
||||
rx, tx, timestamps, err := runFluxForRxTxArray(query, h.queryApi)
|
||||
if err != nil {
|
||||
logrus.Errorf("error running account metrics query for '%v': %v", principal.Email, err)
|
||||
return metadata.NewGetAccountMetricsInternalServerError()
|
||||
}
|
||||
|
||||
response := &rest_model_zrok.Metrics{
|
||||
Scope: "account",
|
||||
ID: fmt.Sprintf("%d", principal.ID),
|
||||
Period: duration.Seconds(),
|
||||
}
|
||||
for i := 0; i < len(rx) && i < len(tx) && i < len(timestamps); i++ {
|
||||
response.Samples = append(response.Samples, &rest_model_zrok.MetricsSample{
|
||||
Rx: rx[i],
|
||||
Tx: tx[i],
|
||||
Timestamp: timestamps[i],
|
||||
})
|
||||
}
|
||||
return metadata.NewGetAccountMetricsOK().WithPayload(response)
|
||||
}
|
||||
|
||||
type getEnvironmentMetricsHandler struct {
|
||||
cfg *metrics.InfluxConfig
|
||||
idb influxdb2.Client
|
||||
queryApi api.QueryAPI
|
||||
}
|
||||
|
||||
func newGetEnvironmentMetricsHandler(cfg *metrics.InfluxConfig) *getEnvironmentMetricsHandler {
|
||||
idb := influxdb2.NewClient(cfg.Url, cfg.Token)
|
||||
queryApi := idb.QueryAPI(cfg.Org)
|
||||
return &getEnvironmentMetricsHandler{
|
||||
cfg: cfg,
|
||||
idb: idb,
|
||||
queryApi: queryApi,
|
||||
}
|
||||
}
|
||||
|
||||
func (ma *metricsAgent) processMetrics(m *model.Metrics) error {
|
||||
var pts []*write.Point
|
||||
if len(m.Sessions) > 0 {
|
||||
out := "metrics = {\n"
|
||||
for k, v := range m.Sessions {
|
||||
if ma.writeApi != nil {
|
||||
pt := influxdb2.NewPoint("xfer",
|
||||
map[string]string{"namespace": m.Namespace, "share": k},
|
||||
map[string]interface{}{"bytesRead": v.BytesRead, "bytesWritten": v.BytesWritten},
|
||||
time.UnixMilli(v.LastUpdate))
|
||||
pts = append(pts, pt)
|
||||
}
|
||||
out += fmt.Sprintf("\t[%v.%v]: %v/%v (%v)\n", m.Namespace, k, util.BytesToSize(v.BytesRead), util.BytesToSize(v.BytesWritten), time.Since(time.UnixMilli(v.LastUpdate)))
|
||||
}
|
||||
out += "}"
|
||||
logrus.Info(out)
|
||||
func (h *getEnvironmentMetricsHandler) Handle(params metadata.GetEnvironmentMetricsParams, principal *rest_model_zrok.Principal) middleware.Responder {
|
||||
trx, err := str.Begin()
|
||||
if err != nil {
|
||||
logrus.Errorf("error starting transaction: %v", err)
|
||||
return metadata.NewGetEnvironmentMetricsInternalServerError()
|
||||
}
|
||||
defer func() { _ = trx.Rollback() }()
|
||||
env, err := str.FindEnvironmentForAccount(params.EnvID, int(principal.ID), trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding environment '%s' for '%s': %v", params.EnvID, principal.Email, err)
|
||||
return metadata.NewGetEnvironmentMetricsUnauthorized()
|
||||
}
|
||||
|
||||
if len(pts) > 0 {
|
||||
if err := ma.writeApi.WritePoint(context.Background(), pts...); err == nil {
|
||||
logrus.Debugf("wrote metrics to influx")
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ma *metricsAgent) stop() {
|
||||
close(ma.shutdown)
|
||||
}
|
||||
|
||||
func (ma *metricsAgent) join() {
|
||||
<-ma.joined
|
||||
}
|
||||
|
||||
type metricsHandler struct {
|
||||
conn net.Conn
|
||||
metricsQueue chan *model.Metrics
|
||||
}
|
||||
|
||||
func newMetricsHandler(conn net.Conn, metricsQueue chan *model.Metrics) *metricsHandler {
|
||||
return &metricsHandler{conn, metricsQueue}
|
||||
}
|
||||
|
||||
func (mh *metricsHandler) run() {
|
||||
logrus.Debugf("handling metrics connection: %v", mh.conn.RemoteAddr())
|
||||
var mtrBuf bytes.Buffer
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
n, err := mh.conn.Read(buf)
|
||||
duration := 30 * 24 * time.Hour
|
||||
if params.Duration != nil {
|
||||
v, err := time.ParseDuration(*params.Duration)
|
||||
if err != nil {
|
||||
break
|
||||
logrus.Errorf("bad duration '%v' for '%v': %v", *params.Duration, principal.Email, err)
|
||||
return metadata.NewGetAccountMetricsBadRequest()
|
||||
}
|
||||
mtrBuf.Write(buf[:n])
|
||||
duration = v
|
||||
}
|
||||
if err := mh.conn.Close(); err != nil {
|
||||
logrus.Errorf("error closing metrics connection")
|
||||
slice := sliceSize(duration)
|
||||
|
||||
query := fmt.Sprintf("from(bucket: \"%v\")\n", h.cfg.Bucket) +
|
||||
fmt.Sprintf("|> range(start: -%v)\n", duration) +
|
||||
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")\n" +
|
||||
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
||||
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
||||
fmt.Sprintf("|> filter(fn: (r) => r[\"envId\"] == \"%d\")\n", int64(env.Id)) +
|
||||
"|> drop(columns: [\"share\", \"acctId\"])\n" +
|
||||
fmt.Sprintf("|> aggregateWindow(every: %v, fn: sum, createEmpty: true)", slice)
|
||||
|
||||
rx, tx, timestamps, err := runFluxForRxTxArray(query, h.queryApi)
|
||||
if err != nil {
|
||||
logrus.Errorf("error running account metrics query for '%v': %v", principal.Email, err)
|
||||
return metadata.NewGetAccountMetricsInternalServerError()
|
||||
}
|
||||
m := &model.Metrics{}
|
||||
if err := bson.Unmarshal(mtrBuf.Bytes(), &m); err == nil {
|
||||
mh.metricsQueue <- m
|
||||
} else {
|
||||
logrus.Errorf("error unmarshaling metrics: %v", err)
|
||||
|
||||
response := &rest_model_zrok.Metrics{
|
||||
Scope: "account",
|
||||
ID: fmt.Sprintf("%d", principal.ID),
|
||||
Period: duration.Seconds(),
|
||||
}
|
||||
for i := 0; i < len(rx) && i < len(tx) && i < len(timestamps); i++ {
|
||||
response.Samples = append(response.Samples, &rest_model_zrok.MetricsSample{
|
||||
Rx: rx[i],
|
||||
Tx: tx[i],
|
||||
Timestamp: timestamps[i],
|
||||
})
|
||||
}
|
||||
|
||||
return metadata.NewGetEnvironmentMetricsOK().WithPayload(response)
|
||||
}
|
||||
|
||||
type getShareMetricsHandler struct {
|
||||
cfg *metrics.InfluxConfig
|
||||
idb influxdb2.Client
|
||||
queryApi api.QueryAPI
|
||||
}
|
||||
|
||||
func newGetShareMetricsHandler(cfg *metrics.InfluxConfig) *getShareMetricsHandler {
|
||||
idb := influxdb2.NewClient(cfg.Url, cfg.Token)
|
||||
queryApi := idb.QueryAPI(cfg.Org)
|
||||
return &getShareMetricsHandler{
|
||||
cfg: cfg,
|
||||
idb: idb,
|
||||
queryApi: queryApi,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *getShareMetricsHandler) Handle(params metadata.GetShareMetricsParams, principal *rest_model_zrok.Principal) middleware.Responder {
|
||||
trx, err := str.Begin()
|
||||
if err != nil {
|
||||
logrus.Errorf("error starting transaction: %v", err)
|
||||
return metadata.NewGetEnvironmentMetricsInternalServerError()
|
||||
}
|
||||
defer func() { _ = trx.Rollback() }()
|
||||
shr, err := str.FindShareWithToken(params.ShrToken, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding share '%v' for '%v': %v", params.ShrToken, principal.Email, err)
|
||||
return metadata.NewGetShareMetricsUnauthorized()
|
||||
}
|
||||
env, err := str.GetEnvironment(shr.EnvironmentId, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding environment '%d' for '%v': %v", shr.EnvironmentId, principal.Email, err)
|
||||
return metadata.NewGetShareMetricsUnauthorized()
|
||||
}
|
||||
if env.AccountId != nil && int64(*env.AccountId) != principal.ID {
|
||||
logrus.Errorf("user '%v' does not own share '%v'", principal.Email, params.ShrToken)
|
||||
return metadata.NewGetShareMetricsUnauthorized()
|
||||
}
|
||||
|
||||
duration := 30 * 24 * time.Hour
|
||||
if params.Duration != nil {
|
||||
v, err := time.ParseDuration(*params.Duration)
|
||||
if err != nil {
|
||||
logrus.Errorf("bad duration '%v' for '%v': %v", *params.Duration, principal.Email, err)
|
||||
return metadata.NewGetAccountMetricsBadRequest()
|
||||
}
|
||||
duration = v
|
||||
}
|
||||
slice := sliceSize(duration)
|
||||
|
||||
query := fmt.Sprintf("from(bucket: \"%v\")\n", h.cfg.Bucket) +
|
||||
fmt.Sprintf("|> range(start: -%v)\n", duration) +
|
||||
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")\n" +
|
||||
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
||||
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
||||
fmt.Sprintf("|> filter(fn: (r) => r[\"share\"] == \"%v\")\n", shr.Token) +
|
||||
fmt.Sprintf("|> aggregateWindow(every: %v, fn: sum, createEmpty: true)", slice)
|
||||
|
||||
rx, tx, timestamps, err := runFluxForRxTxArray(query, h.queryApi)
|
||||
if err != nil {
|
||||
logrus.Errorf("error running account metrics query for '%v': %v", principal.Email, err)
|
||||
return metadata.NewGetAccountMetricsInternalServerError()
|
||||
}
|
||||
|
||||
response := &rest_model_zrok.Metrics{
|
||||
Scope: "account",
|
||||
ID: fmt.Sprintf("%d", principal.ID),
|
||||
Period: duration.Seconds(),
|
||||
}
|
||||
for i := 0; i < len(rx) && i < len(tx) && i < len(timestamps); i++ {
|
||||
response.Samples = append(response.Samples, &rest_model_zrok.MetricsSample{
|
||||
Rx: rx[i],
|
||||
Tx: tx[i],
|
||||
Timestamp: timestamps[i],
|
||||
})
|
||||
}
|
||||
|
||||
return metadata.NewGetShareMetricsOK().WithPayload(response)
|
||||
}
|
||||
|
||||
func runFluxForRxTxArray(query string, queryApi api.QueryAPI) (rx, tx, timestamps []float64, err error) {
|
||||
result, err := queryApi.Query(context.Background(), query)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
for result.Next() {
|
||||
switch result.Record().Field() {
|
||||
case "rx":
|
||||
rxV := int64(0)
|
||||
if v, ok := result.Record().Value().(int64); ok {
|
||||
rxV = v
|
||||
}
|
||||
rx = append(rx, float64(rxV))
|
||||
timestamps = append(timestamps, float64(result.Record().Time().UnixMilli()))
|
||||
|
||||
case "tx":
|
||||
txV := int64(0)
|
||||
if v, ok := result.Record().Value().(int64); ok {
|
||||
txV = v
|
||||
}
|
||||
tx = append(tx, float64(txV))
|
||||
}
|
||||
}
|
||||
return rx, tx, timestamps, nil
|
||||
}
|
||||
|
||||
func sliceSize(duration time.Duration) time.Duration {
|
||||
switch duration {
|
||||
case 30 * 24 * time.Hour:
|
||||
return 24 * time.Hour
|
||||
case 7 * 24 * time.Hour:
|
||||
return 4 * time.Hour
|
||||
case 24 * time.Hour:
|
||||
return 30 * time.Minute
|
||||
default:
|
||||
return duration
|
||||
}
|
||||
}
|
||||
|
80
controller/metrics/agent.go
Normal file
80
controller/metrics/agent.go
Normal file
@ -0,0 +1,80 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Agent struct {
|
||||
events chan ZitiEventMsg
|
||||
src ZitiEventJsonSource
|
||||
srcJoin chan struct{}
|
||||
cache *cache
|
||||
snks []UsageSink
|
||||
}
|
||||
|
||||
func NewAgent(cfg *AgentConfig, str *store.Store, ifxCfg *InfluxConfig) (*Agent, error) {
|
||||
a := &Agent{}
|
||||
if v, ok := cfg.Source.(ZitiEventJsonSource); ok {
|
||||
a.src = v
|
||||
} else {
|
||||
return nil, errors.New("invalid event json source")
|
||||
}
|
||||
a.cache = newShareCache(str)
|
||||
a.snks = append(a.snks, newInfluxWriter(ifxCfg))
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *Agent) AddUsageSink(snk UsageSink) {
|
||||
a.snks = append(a.snks, snk)
|
||||
}
|
||||
|
||||
func (a *Agent) Start() error {
|
||||
a.events = make(chan ZitiEventMsg)
|
||||
srcJoin, err := a.src.Start(a.events)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.srcJoin = srcJoin
|
||||
|
||||
go func() {
|
||||
logrus.Info("started")
|
||||
defer logrus.Info("stopped")
|
||||
for {
|
||||
select {
|
||||
case event := <-a.events:
|
||||
if usage, err := Ingest(event.Data()); err == nil {
|
||||
if usage.ZitiServiceId != "" {
|
||||
if err := a.cache.addZrokDetail(usage); err != nil {
|
||||
logrus.Errorf("unable to add zrok detail for: %v: %v", usage.String(), err)
|
||||
}
|
||||
}
|
||||
shouldAck := true
|
||||
for _, snk := range a.snks {
|
||||
if err := snk.Handle(usage); err != nil {
|
||||
logrus.Errorf("error handling usage: %v", err)
|
||||
if shouldAck {
|
||||
shouldAck = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if shouldAck {
|
||||
if err := event.Ack(); err != nil {
|
||||
logrus.Errorf("unable to ack handled message: %v", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("unable to ingest '%v': %v", event.Data(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) Stop() {
|
||||
a.src.Stop()
|
||||
close(a.events)
|
||||
}
|
81
controller/metrics/amqpSink.go
Normal file
81
controller/metrics/amqpSink.go
Normal file
@ -0,0 +1,81 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/controller/env"
|
||||
"github.com/pkg/errors"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
env.GetCfOptions().AddFlexibleSetter("amqpSink", loadAmqpSinkConfig)
|
||||
}
|
||||
|
||||
type AmqpSinkConfig struct {
|
||||
Url string `cf:"+secret"`
|
||||
QueueName string
|
||||
}
|
||||
|
||||
func loadAmqpSinkConfig(v interface{}, _ *cf.Options) (interface{}, error) {
|
||||
if submap, ok := v.(map[string]interface{}); ok {
|
||||
cfg := &AmqpSinkConfig{}
|
||||
if err := cf.Bind(cfg, submap, cf.DefaultOptions()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newAmqpSink(cfg)
|
||||
}
|
||||
return nil, errors.New("invalid config structure for 'amqpSink'")
|
||||
}
|
||||
|
||||
type amqpSink struct {
|
||||
cfg *AmqpSinkConfig
|
||||
conn *amqp.Connection
|
||||
ch *amqp.Channel
|
||||
queue amqp.Queue
|
||||
connected bool
|
||||
}
|
||||
|
||||
func newAmqpSink(cfg *AmqpSinkConfig) (*amqpSink, error) {
|
||||
as := &amqpSink{cfg: cfg}
|
||||
return as, nil
|
||||
}
|
||||
|
||||
func (s *amqpSink) Handle(event ZitiEventJson) error {
|
||||
if !s.connected {
|
||||
if err := s.connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Infof("connected to '%v'", s.cfg.Url)
|
||||
s.connected = true
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
logrus.Infof("pushing '%v'", event)
|
||||
err := s.ch.PublishWithContext(ctx, "", s.queue.Name, false, false, amqp.Publishing{
|
||||
ContentType: "application/json",
|
||||
Body: []byte(event),
|
||||
})
|
||||
if err != nil {
|
||||
s.connected = false
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *amqpSink) connect() (err error) {
|
||||
s.conn, err = amqp.Dial(s.cfg.Url)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error dialing '%v'", s.cfg.Url)
|
||||
}
|
||||
s.ch, err = s.conn.Channel()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error getting amqp channel from '%v'", s.cfg.Url)
|
||||
}
|
||||
s.queue, err = s.ch.QueueDeclare(s.cfg.QueueName, true, false, false, false, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error declaring queue '%v' with '%v'", s.cfg.QueueName, s.cfg.Url)
|
||||
}
|
||||
return nil
|
||||
}
|
143
controller/metrics/amqpSource.go
Normal file
143
controller/metrics/amqpSource.go
Normal file
@ -0,0 +1,143 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/zrok/controller/env"
|
||||
"github.com/pkg/errors"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
env.GetCfOptions().AddFlexibleSetter("amqpSource", loadAmqpSourceConfig)
|
||||
}
|
||||
|
||||
type AmqpSourceConfig struct {
|
||||
Url string `cf:"+secret"`
|
||||
QueueName string
|
||||
}
|
||||
|
||||
func loadAmqpSourceConfig(v interface{}, _ *cf.Options) (interface{}, error) {
|
||||
if submap, ok := v.(map[string]interface{}); ok {
|
||||
cfg := &AmqpSourceConfig{}
|
||||
if err := cf.Bind(cfg, submap, cf.DefaultOptions()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newAmqpSource(cfg)
|
||||
}
|
||||
return nil, errors.New("invalid config structure for 'amqpSource'")
|
||||
}
|
||||
|
||||
type amqpSource struct {
|
||||
cfg *AmqpSourceConfig
|
||||
conn *amqp.Connection
|
||||
ch *amqp.Channel
|
||||
queue amqp.Queue
|
||||
msgs <-chan amqp.Delivery
|
||||
errs chan *amqp.Error
|
||||
events chan ZitiEventMsg
|
||||
close chan struct{}
|
||||
join chan struct{}
|
||||
}
|
||||
|
||||
func newAmqpSource(cfg *AmqpSourceConfig) (*amqpSource, error) {
|
||||
as := &amqpSource{
|
||||
cfg: cfg,
|
||||
close: make(chan struct{}),
|
||||
join: make(chan struct{}),
|
||||
}
|
||||
return as, nil
|
||||
}
|
||||
|
||||
func (s *amqpSource) Start(events chan ZitiEventMsg) (join chan struct{}, err error) {
|
||||
s.events = events
|
||||
go s.run()
|
||||
return s.join, nil
|
||||
}
|
||||
|
||||
func (s *amqpSource) Stop() {
|
||||
close(s.close)
|
||||
<-s.join
|
||||
}
|
||||
|
||||
func (s *amqpSource) run() {
|
||||
logrus.Info("started")
|
||||
defer logrus.Info("stopped")
|
||||
defer close(s.join)
|
||||
|
||||
mainLoop:
|
||||
for {
|
||||
logrus.Infof("connecting to '%v'", s.cfg.Url)
|
||||
if err := s.connect(); err != nil {
|
||||
logrus.Errorf("error connecting to '%v': %v", s.cfg.Url, err)
|
||||
select {
|
||||
case <-time.After(10 * time.Second):
|
||||
continue mainLoop
|
||||
case <-s.close:
|
||||
break mainLoop
|
||||
}
|
||||
}
|
||||
logrus.Infof("connected to '%v'", s.cfg.Url)
|
||||
|
||||
msgLoop:
|
||||
for {
|
||||
select {
|
||||
case err, ok := <-s.errs:
|
||||
if err != nil || !ok {
|
||||
logrus.Error(err)
|
||||
break msgLoop
|
||||
}
|
||||
|
||||
case <-s.close:
|
||||
break mainLoop
|
||||
|
||||
case event, ok := <-s.msgs:
|
||||
if !ok {
|
||||
logrus.Debug("selecting on msg !ok")
|
||||
break msgLoop
|
||||
}
|
||||
if event.Body != nil {
|
||||
s.events <- &ZitiEventAMQP{
|
||||
data: ZitiEventJson(event.Body),
|
||||
msg: event,
|
||||
}
|
||||
} else {
|
||||
logrus.Debug("event body was nil!")
|
||||
break msgLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *amqpSource) connect() error {
|
||||
conn, err := amqp.Dial(s.cfg.Url)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error dialing amqp broker")
|
||||
}
|
||||
|
||||
ch, err := conn.Channel()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting amqp channel")
|
||||
}
|
||||
|
||||
queue, err := ch.QueueDeclare(s.cfg.QueueName, true, false, false, false, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error declaring queue")
|
||||
}
|
||||
|
||||
msgs, err := ch.Consume(s.cfg.QueueName, "zrok", false, false, false, false, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error consuming")
|
||||
}
|
||||
|
||||
s.errs = make(chan *amqp.Error)
|
||||
conn.NotifyClose(s.errs)
|
||||
s.conn = conn
|
||||
s.ch = ch
|
||||
s.queue = queue
|
||||
s.msgs = msgs
|
||||
|
||||
return nil
|
||||
}
|
78
controller/metrics/bridge.go
Normal file
78
controller/metrics/bridge.go
Normal file
@ -0,0 +1,78 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type BridgeConfig struct {
|
||||
Source interface{}
|
||||
Sink interface{}
|
||||
}
|
||||
|
||||
type Bridge struct {
|
||||
src ZitiEventJsonSource
|
||||
srcJoin chan struct{}
|
||||
snk ZitiEventJsonSink
|
||||
events chan ZitiEventMsg
|
||||
close chan struct{}
|
||||
join chan struct{}
|
||||
}
|
||||
|
||||
func NewBridge(cfg *BridgeConfig) (*Bridge, error) {
|
||||
b := &Bridge{
|
||||
events: make(chan ZitiEventMsg),
|
||||
join: make(chan struct{}),
|
||||
close: make(chan struct{}),
|
||||
}
|
||||
if v, ok := cfg.Source.(ZitiEventJsonSource); ok {
|
||||
b.src = v
|
||||
} else {
|
||||
return nil, errors.New("invalid source type")
|
||||
}
|
||||
if v, ok := cfg.Sink.(ZitiEventJsonSink); ok {
|
||||
b.snk = v
|
||||
} else {
|
||||
return nil, errors.New("invalid sink type")
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b *Bridge) Start() (join chan struct{}, err error) {
|
||||
if b.srcJoin, err = b.src.Start(b.events); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
logrus.Info("started")
|
||||
defer logrus.Info("stopped")
|
||||
defer close(b.join)
|
||||
|
||||
eventLoop:
|
||||
for {
|
||||
select {
|
||||
case eventJson := <-b.events:
|
||||
logrus.Info(eventJson)
|
||||
if err := b.snk.Handle(eventJson.Data()); err == nil {
|
||||
logrus.Infof("-> %v", eventJson.Data())
|
||||
} else {
|
||||
logrus.Error(err)
|
||||
}
|
||||
eventJson.Ack()
|
||||
|
||||
case <-b.close:
|
||||
logrus.Info("received close signal")
|
||||
break eventLoop
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return b.join, nil
|
||||
}
|
||||
|
||||
func (b *Bridge) Stop() {
|
||||
b.src.Stop()
|
||||
close(b.close)
|
||||
<-b.srcJoin
|
||||
<-b.join
|
||||
}
|
35
controller/metrics/cache.go
Normal file
35
controller/metrics/cache.go
Normal file
@ -0,0 +1,35 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
)
|
||||
|
||||
type cache struct {
|
||||
str *store.Store
|
||||
}
|
||||
|
||||
func newShareCache(str *store.Store) *cache {
|
||||
return &cache{str}
|
||||
}
|
||||
|
||||
func (c *cache) addZrokDetail(u *Usage) error {
|
||||
tx, err := c.str.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
shr, err := c.str.FindShareWithZIdAndDeleted(u.ZitiServiceId, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.ShareToken = shr.Token
|
||||
env, err := c.str.GetEnvironment(shr.EnvironmentId, tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.EnvironmentId = int64(env.Id)
|
||||
u.AccountId = int64(*env.AccountId)
|
||||
|
||||
return nil
|
||||
}
|
17
controller/metrics/config.go
Normal file
17
controller/metrics/config.go
Normal file
@ -0,0 +1,17 @@
|
||||
package metrics
|
||||
|
||||
type Config struct {
|
||||
Influx *InfluxConfig
|
||||
Agent *AgentConfig
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
Source interface{}
|
||||
}
|
||||
|
||||
type InfluxConfig struct {
|
||||
Url string
|
||||
Bucket string
|
||||
Org string
|
||||
Token string `cf:"+secret"`
|
||||
}
|
133
controller/metrics/fileSource.go
Normal file
133
controller/metrics/fileSource.go
Normal file
@ -0,0 +1,133 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"os"
|
||||
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/nxadm/tail"
|
||||
"github.com/openziti/zrok/controller/env"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
env.GetCfOptions().AddFlexibleSetter("fileSource", loadFileSourceConfig)
|
||||
}
|
||||
|
||||
type FileSourceConfig struct {
|
||||
Path string
|
||||
PointerPath string
|
||||
}
|
||||
|
||||
func loadFileSourceConfig(v interface{}, _ *cf.Options) (interface{}, error) {
|
||||
if submap, ok := v.(map[string]interface{}); ok {
|
||||
cfg := &FileSourceConfig{}
|
||||
if err := cf.Bind(cfg, submap, cf.DefaultOptions()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fileSource{cfg: cfg}, nil
|
||||
}
|
||||
return nil, errors.New("invalid config structure for 'fileSource'")
|
||||
}
|
||||
|
||||
type fileSource struct {
|
||||
cfg *FileSourceConfig
|
||||
ptrF *os.File
|
||||
t *tail.Tail
|
||||
}
|
||||
|
||||
func (s *fileSource) Start(events chan ZitiEventMsg) (join chan struct{}, err error) {
|
||||
f, err := os.Open(s.cfg.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error opening '%v'", s.cfg.Path)
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
s.ptrF, err = os.OpenFile(s.pointerPath(), os.O_CREATE|os.O_RDWR, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error opening pointer '%v'", s.pointerPath())
|
||||
}
|
||||
|
||||
ptr, err := s.readPtr()
|
||||
if err != nil {
|
||||
logrus.Errorf("error reading pointer: %v", err)
|
||||
}
|
||||
logrus.Infof("retrieved stored position pointer at '%d'", ptr)
|
||||
|
||||
join = make(chan struct{})
|
||||
go func() {
|
||||
s.tail(ptr, events)
|
||||
close(join)
|
||||
}()
|
||||
|
||||
return join, nil
|
||||
}
|
||||
|
||||
func (s *fileSource) Stop() {
|
||||
if err := s.t.Stop(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fileSource) tail(ptr int64, events chan ZitiEventMsg) {
|
||||
logrus.Info("started")
|
||||
defer logrus.Info("stopped")
|
||||
|
||||
var err error
|
||||
s.t, err = tail.TailFile(s.cfg.Path, tail.Config{
|
||||
ReOpen: true,
|
||||
Follow: true,
|
||||
Location: &tail.SeekInfo{Offset: ptr},
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Errorf("error starting tail: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for event := range s.t.Lines {
|
||||
events <- &ZitiEventJsonMsg{
|
||||
data: ZitiEventJson(event.Text),
|
||||
}
|
||||
|
||||
if err := s.writePtr(event.SeekInfo.Offset); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fileSource) pointerPath() string {
|
||||
if s.cfg.PointerPath == "" {
|
||||
return s.cfg.Path + ".ptr"
|
||||
} else {
|
||||
return s.cfg.PointerPath
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fileSource) readPtr() (int64, error) {
|
||||
ptr := int64(0)
|
||||
buf := make([]byte, 8)
|
||||
if n, err := s.ptrF.Seek(0, 0); err == nil && n == 0 {
|
||||
if n, err := s.ptrF.Read(buf); err == nil && n == 8 {
|
||||
ptr = int64(binary.LittleEndian.Uint64(buf))
|
||||
return ptr, nil
|
||||
} else {
|
||||
return 0, errors.Wrapf(err, "error reading pointer (%d): %v", n, err)
|
||||
}
|
||||
} else {
|
||||
return 0, errors.Wrapf(err, "error seeking pointer (%d): %v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fileSource) writePtr(ptr int64) error {
|
||||
buf := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(buf, uint64(ptr))
|
||||
if n, err := s.ptrF.Seek(0, 0); err == nil && n == 0 {
|
||||
if n, err := s.ptrF.Write(buf); err != nil || n != 8 {
|
||||
return errors.Wrapf(err, "error writing pointer (%d): %v", n, err)
|
||||
}
|
||||
} else {
|
||||
return errors.Wrapf(err, "error seeking pointer (%d): %v", n, err)
|
||||
}
|
||||
return nil
|
||||
}
|
63
controller/metrics/influxWriter.go
Normal file
63
controller/metrics/influxWriter.go
Normal file
@ -0,0 +1,63 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
|
||||
"github.com/influxdata/influxdb-client-go/v2/api"
|
||||
"github.com/influxdata/influxdb-client-go/v2/api/write"
|
||||
"github.com/openziti/zrok/util"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type influxWriter struct {
|
||||
idb influxdb2.Client
|
||||
writeApi api.WriteAPIBlocking
|
||||
}
|
||||
|
||||
func newInfluxWriter(cfg *InfluxConfig) *influxWriter {
|
||||
idb := influxdb2.NewClient(cfg.Url, cfg.Token)
|
||||
writeApi := idb.WriteAPIBlocking(cfg.Org, cfg.Bucket)
|
||||
return &influxWriter{idb, writeApi}
|
||||
}
|
||||
|
||||
func (w *influxWriter) Handle(u *Usage) error {
|
||||
if u.ShareToken != "" {
|
||||
out := fmt.Sprintf("share: %v, circuit: %v", u.ShareToken, u.ZitiCircuitId)
|
||||
|
||||
envId := fmt.Sprintf("%d", u.EnvironmentId)
|
||||
acctId := fmt.Sprintf("%d", u.AccountId)
|
||||
|
||||
var pts []*write.Point
|
||||
circuitPt := influxdb2.NewPoint("circuits",
|
||||
map[string]string{"share": u.ShareToken, "envId": envId, "acctId": acctId},
|
||||
map[string]interface{}{"circuit": u.ZitiCircuitId},
|
||||
u.IntervalStart)
|
||||
pts = append(pts, circuitPt)
|
||||
|
||||
if u.BackendTx > 0 || u.BackendRx > 0 {
|
||||
pt := influxdb2.NewPoint("xfer",
|
||||
map[string]string{"namespace": "backend", "share": u.ShareToken, "envId": envId, "acctId": acctId},
|
||||
map[string]interface{}{"rx": u.BackendRx, "tx": u.BackendTx},
|
||||
u.IntervalStart)
|
||||
pts = append(pts, pt)
|
||||
out += fmt.Sprintf(" backend {rx: %v, tx: %v}", util.BytesToSize(u.BackendRx), util.BytesToSize(u.BackendTx))
|
||||
}
|
||||
if u.FrontendTx > 0 || u.FrontendRx > 0 {
|
||||
pt := influxdb2.NewPoint("xfer",
|
||||
map[string]string{"namespace": "frontend", "share": u.ShareToken, "envId": envId, "acctId": acctId},
|
||||
map[string]interface{}{"rx": u.FrontendRx, "tx": u.FrontendTx},
|
||||
u.IntervalStart)
|
||||
pts = append(pts, pt)
|
||||
out += fmt.Sprintf(" frontend {rx: %v, tx: %v}", util.BytesToSize(u.FrontendRx), util.BytesToSize(u.FrontendTx))
|
||||
}
|
||||
|
||||
if err := w.writeApi.WritePoint(context.Background(), pts...); err == nil {
|
||||
logrus.Info(out)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
83
controller/metrics/model.go
Normal file
83
controller/metrics/model.go
Normal file
@ -0,0 +1,83 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/openziti/zrok/util"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
type Usage struct {
|
||||
ProcessedStamp time.Time
|
||||
IntervalStart time.Time
|
||||
ZitiServiceId string
|
||||
ZitiCircuitId string
|
||||
ShareToken string
|
||||
EnvironmentId int64
|
||||
AccountId int64
|
||||
FrontendTx int64
|
||||
FrontendRx int64
|
||||
BackendTx int64
|
||||
BackendRx int64
|
||||
}
|
||||
|
||||
func (u Usage) String() string {
|
||||
out := "Usage {"
|
||||
out += fmt.Sprintf("processed '%v'", u.ProcessedStamp)
|
||||
out += ", " + fmt.Sprintf("interval '%v'", u.IntervalStart)
|
||||
out += ", " + fmt.Sprintf("service '%v'", u.ZitiServiceId)
|
||||
out += ", " + fmt.Sprintf("circuit '%v'", u.ZitiCircuitId)
|
||||
out += ", " + fmt.Sprintf("share '%v'", u.ShareToken)
|
||||
out += ", " + fmt.Sprintf("environment '%d'", u.EnvironmentId)
|
||||
out += ", " + fmt.Sprintf("account '%v'", u.AccountId)
|
||||
out += ", " + fmt.Sprintf("fe {rx %v, tx %v}", util.BytesToSize(u.FrontendRx), util.BytesToSize(u.FrontendTx))
|
||||
out += ", " + fmt.Sprintf("be {rx %v, tx %v}", util.BytesToSize(u.BackendRx), util.BytesToSize(u.BackendTx))
|
||||
out += "}"
|
||||
return out
|
||||
}
|
||||
|
||||
type UsageSink interface {
|
||||
Handle(u *Usage) error
|
||||
}
|
||||
|
||||
type ZitiEventJson string
|
||||
|
||||
type ZitiEventJsonMsg struct {
|
||||
data ZitiEventJson
|
||||
}
|
||||
|
||||
func (e *ZitiEventJsonMsg) Data() ZitiEventJson {
|
||||
return e.data
|
||||
}
|
||||
|
||||
func (e *ZitiEventJsonMsg) Ack() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ZitiEventAMQP struct {
|
||||
data ZitiEventJson
|
||||
msg amqp.Delivery
|
||||
}
|
||||
|
||||
func (e *ZitiEventAMQP) Data() ZitiEventJson {
|
||||
return e.data
|
||||
}
|
||||
|
||||
func (e *ZitiEventAMQP) Ack() error {
|
||||
return e.msg.Ack(false)
|
||||
}
|
||||
|
||||
type ZitiEventMsg interface {
|
||||
Data() ZitiEventJson
|
||||
Ack() error
|
||||
}
|
||||
|
||||
type ZitiEventJsonSource interface {
|
||||
Start(chan ZitiEventMsg) (join chan struct{}, err error)
|
||||
Stop()
|
||||
}
|
||||
|
||||
type ZitiEventJsonSink interface {
|
||||
Handle(event ZitiEventJson) error
|
||||
}
|
94
controller/metrics/usageIngest.go
Normal file
94
controller/metrics/usageIngest.go
Normal file
@ -0,0 +1,94 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Ingest(event ZitiEventJson) (*Usage, error) {
|
||||
eventMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(event), &eventMap); err == nil {
|
||||
u := &Usage{ProcessedStamp: time.Now()}
|
||||
if ns, found := eventMap["namespace"]; found && ns == "fabric.usage" {
|
||||
if v, found := eventMap["interval_start_utc"]; found {
|
||||
if vFloat64, ok := v.(float64); ok {
|
||||
u.IntervalStart = time.Unix(int64(vFloat64), 0)
|
||||
} else {
|
||||
logrus.Errorf("unable to assert 'interval_start_utc': %v", event)
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("missing 'interval_start_utc': %v", event)
|
||||
}
|
||||
if v, found := eventMap["tags"]; found {
|
||||
if tags, ok := v.(map[string]interface{}); ok {
|
||||
if v, found := tags["serviceId"]; found {
|
||||
if vStr, ok := v.(string); ok {
|
||||
u.ZitiServiceId = vStr
|
||||
} else {
|
||||
logrus.Errorf("unable to assert 'tags/serviceId': %v", event)
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("missing 'tags/serviceId': %v", event)
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("unable to assert 'tags': %v", event)
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("missing 'tags': %v", event)
|
||||
}
|
||||
if v, found := eventMap["usage"]; found {
|
||||
if usage, ok := v.(map[string]interface{}); ok {
|
||||
if v, found := usage["ingress.tx"]; found {
|
||||
if vFloat64, ok := v.(float64); ok {
|
||||
u.FrontendTx = int64(vFloat64)
|
||||
} else {
|
||||
logrus.Errorf("unable to assert 'usage/ingress.tx': %v", event)
|
||||
}
|
||||
}
|
||||
if v, found := usage["ingress.rx"]; found {
|
||||
if vFloat64, ok := v.(float64); ok {
|
||||
u.FrontendRx = int64(vFloat64)
|
||||
} else {
|
||||
logrus.Errorf("unable to assert 'usage/ingress.rx': %v", event)
|
||||
}
|
||||
}
|
||||
if v, found := usage["egress.tx"]; found {
|
||||
if vFloat64, ok := v.(float64); ok {
|
||||
u.BackendRx = int64(vFloat64)
|
||||
} else {
|
||||
logrus.Errorf("unable to assert 'usage/egress.tx': %v", event)
|
||||
}
|
||||
}
|
||||
if v, found := usage["egress.rx"]; found {
|
||||
if vFloat64, ok := v.(float64); ok {
|
||||
u.BackendTx = int64(vFloat64)
|
||||
} else {
|
||||
logrus.Errorf("unable to assert 'usage/egress.rx': %v", event)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("unable to assert 'usage' (%v) %v", reflect.TypeOf(v), event)
|
||||
}
|
||||
} else {
|
||||
logrus.Warnf("missing 'usage': %v", event)
|
||||
}
|
||||
if v, found := eventMap["circuit_id"]; found {
|
||||
if vStr, ok := v.(string); ok {
|
||||
u.ZitiCircuitId = vStr
|
||||
} else {
|
||||
logrus.Errorf("unable to assert 'circuit_id': %v", event)
|
||||
}
|
||||
} else {
|
||||
logrus.Warnf("missing 'circuit_id': %v", event)
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("not 'fabric.usage': %v", event)
|
||||
}
|
||||
return u, nil
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "error unmarshaling")
|
||||
}
|
||||
}
|
158
controller/metrics/websocketSource.go
Normal file
158
controller/metrics/websocketSource.go
Normal file
@ -0,0 +1,158 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/michaelquigley/cf"
|
||||
"github.com/openziti/channel/v2"
|
||||
"github.com/openziti/channel/v2/websockets"
|
||||
"github.com/openziti/edge-api/rest_util"
|
||||
"github.com/openziti/fabric/event"
|
||||
"github.com/openziti/fabric/pb/mgmt_pb"
|
||||
"github.com/openziti/identity"
|
||||
"github.com/openziti/zrok/controller/env"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const ZitiSession = "zt-session"
|
||||
|
||||
func init() {
|
||||
env.GetCfOptions().AddFlexibleSetter("websocketSource", loadWebsocketSourceConfig)
|
||||
}
|
||||
|
||||
type WebsocketSourceConfig struct {
|
||||
WebsocketEndpoint string // wss://127.0.0.1:1280/fabric/v1/ws-api
|
||||
ApiEndpoint string // https://127.0.0.1:1280
|
||||
Username string
|
||||
Password string `cf:"+secret"`
|
||||
}
|
||||
|
||||
func loadWebsocketSourceConfig(v interface{}, _ *cf.Options) (interface{}, error) {
|
||||
if submap, ok := v.(map[string]interface{}); ok {
|
||||
cfg := &WebsocketSourceConfig{}
|
||||
if err := cf.Bind(cfg, submap, cf.DefaultOptions()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &websocketSource{cfg: cfg}, nil
|
||||
}
|
||||
return nil, errors.New("invalid config structure for 'websocketSource'")
|
||||
}
|
||||
|
||||
type websocketSource struct {
|
||||
cfg *WebsocketSourceConfig
|
||||
ch channel.Channel
|
||||
events chan ZitiEventMsg
|
||||
join chan struct{}
|
||||
}
|
||||
|
||||
func (s *websocketSource) Start(events chan ZitiEventMsg) (join chan struct{}, err error) {
|
||||
caCerts, err := rest_util.GetControllerWellKnownCas(s.cfg.ApiEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caPool := x509.NewCertPool()
|
||||
for _, ca := range caCerts {
|
||||
caPool.AddCert(ca)
|
||||
}
|
||||
|
||||
authenticator := rest_util.NewAuthenticatorUpdb(s.cfg.Username, s.cfg.Password)
|
||||
authenticator.RootCas = caPool
|
||||
|
||||
apiEndpointUrl, err := url.Parse(s.cfg.ApiEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiSession, err := authenticator.Authenticate(apiEndpointUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dialer := &websocket.Dialer{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: caPool,
|
||||
},
|
||||
HandshakeTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
conn, resp, err := dialer.Dial(s.cfg.WebsocketEndpoint, http.Header{ZitiSession: []string{*apiSession.Token}})
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
if body, rerr := io.ReadAll(resp.Body); rerr == nil {
|
||||
logrus.Errorf("response body '%v': %v", string(body), err)
|
||||
}
|
||||
} else {
|
||||
logrus.Errorf("no response from websocket dial: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
id := &identity.TokenId{Token: "mgmt"}
|
||||
underlayFactory := websockets.NewUnderlayFactory(id, conn, nil)
|
||||
|
||||
s.join = make(chan struct{})
|
||||
s.events = events
|
||||
bindHandler := func(binding channel.Binding) error {
|
||||
binding.AddReceiveHandler(int32(mgmt_pb.ContentType_StreamEventsEventType), s)
|
||||
binding.AddCloseHandler(channel.CloseHandlerF(func(ch channel.Channel) {
|
||||
close(s.join)
|
||||
}))
|
||||
return nil
|
||||
}
|
||||
|
||||
s.ch, err = channel.NewChannel("mgmt", underlayFactory, channel.BindHandlerF(bindHandler), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
streamEventsRequest := map[string]interface{}{}
|
||||
streamEventsRequest["format"] = "json"
|
||||
streamEventsRequest["subscriptions"] = []*event.Subscription{
|
||||
{
|
||||
Type: "fabric.usage",
|
||||
Options: map[string]interface{}{
|
||||
"version": uint8(3),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
msgBytes, err := json.Marshal(streamEventsRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
requestMsg := channel.NewMessage(int32(mgmt_pb.ContentType_StreamEventsRequestType), msgBytes)
|
||||
responseMsg, err := requestMsg.WithTimeout(5 * time.Second).SendForReply(s.ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if responseMsg.ContentType == channel.ContentTypeResultType {
|
||||
result := channel.UnmarshalResult(responseMsg)
|
||||
if result.Success {
|
||||
logrus.Infof("event stream started: %v", result.Message)
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "error starting event streaming")
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Errorf("unexpected response type %v", responseMsg.ContentType)
|
||||
}
|
||||
|
||||
return s.join, nil
|
||||
}
|
||||
|
||||
func (s *websocketSource) Stop() {
|
||||
_ = s.ch.Close()
|
||||
}
|
||||
|
||||
func (s *websocketSource) HandleReceive(msg *channel.Message, _ channel.Channel) {
|
||||
s.events <- &ZitiEventJsonMsg{
|
||||
data: ZitiEventJson(msg.Body),
|
||||
}
|
||||
}
|
@ -2,41 +2,63 @@ package controller
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/metadata"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func overviewHandler(_ metadata.OverviewParams, principal *rest_model_zrok.Principal) middleware.Responder {
|
||||
tx, err := str.Begin()
|
||||
type overviewHandler struct{}
|
||||
|
||||
func newOverviewHandler() *overviewHandler {
|
||||
return &overviewHandler{}
|
||||
}
|
||||
|
||||
func (h *overviewHandler) Handle(_ metadata.OverviewParams, principal *rest_model_zrok.Principal) middleware.Responder {
|
||||
trx, err := str.Begin()
|
||||
if err != nil {
|
||||
logrus.Errorf("error starting transaction: %v", err)
|
||||
return metadata.NewOverviewInternalServerError()
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx)
|
||||
defer func() { _ = trx.Rollback() }()
|
||||
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding environments for '%v': %v", principal.Email, err)
|
||||
return metadata.NewOverviewInternalServerError()
|
||||
}
|
||||
var out rest_model_zrok.EnvironmentSharesList
|
||||
elm, err := newEnvironmentsLimitedMap(envs, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding limited environments for '%v': %v", principal.Email, err)
|
||||
return metadata.NewOverviewInternalServerError()
|
||||
}
|
||||
accountLimited, err := h.isAccountLimited(principal, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error checking account limited for '%v': %v", principal.Email, err)
|
||||
}
|
||||
ovr := &rest_model_zrok.Overview{AccountLimited: accountLimited}
|
||||
for _, env := range envs {
|
||||
shrs, err := str.FindSharesForEnvironment(env.Id, tx)
|
||||
envRes := &rest_model_zrok.EnvironmentAndResources{
|
||||
Environment: &rest_model_zrok.Environment{
|
||||
Address: env.Address,
|
||||
Description: env.Description,
|
||||
Host: env.Host,
|
||||
ZID: env.ZId,
|
||||
Limited: elm.isLimited(env),
|
||||
CreatedAt: env.CreatedAt.UnixMilli(),
|
||||
UpdatedAt: env.UpdatedAt.UnixMilli(),
|
||||
},
|
||||
}
|
||||
shrs, err := str.FindSharesForEnvironment(env.Id, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding shares for environment '%v': %v", env.ZId, err)
|
||||
return metadata.NewOverviewInternalServerError()
|
||||
}
|
||||
es := &rest_model_zrok.EnvironmentShares{
|
||||
Environment: &rest_model_zrok.Environment{
|
||||
Address: env.Address,
|
||||
CreatedAt: env.CreatedAt.UnixMilli(),
|
||||
Description: env.Description,
|
||||
Host: env.Host,
|
||||
UpdatedAt: env.UpdatedAt.UnixMilli(),
|
||||
ZID: env.ZId,
|
||||
},
|
||||
slm, err := newSharesLimitedMap(shrs, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding limited shares for environment '%v': %v", env.ZId, err)
|
||||
return metadata.NewOverviewInternalServerError()
|
||||
}
|
||||
|
||||
for _, shr := range shrs {
|
||||
feEndpoint := ""
|
||||
if shr.FrontendEndpoint != nil {
|
||||
@ -50,7 +72,7 @@ func overviewHandler(_ metadata.OverviewParams, principal *rest_model_zrok.Princ
|
||||
if shr.BackendProxyEndpoint != nil {
|
||||
beProxyEndpoint = *shr.BackendProxyEndpoint
|
||||
}
|
||||
es.Shares = append(es.Shares, &rest_model_zrok.Share{
|
||||
envShr := &rest_model_zrok.Share{
|
||||
Token: shr.Token,
|
||||
ZID: shr.ZId,
|
||||
ShareMode: shr.ShareMode,
|
||||
@ -59,11 +81,104 @@ func overviewHandler(_ metadata.OverviewParams, principal *rest_model_zrok.Princ
|
||||
FrontendEndpoint: feEndpoint,
|
||||
BackendProxyEndpoint: beProxyEndpoint,
|
||||
Reserved: shr.Reserved,
|
||||
Limited: slm.isLimited(shr),
|
||||
CreatedAt: shr.CreatedAt.UnixMilli(),
|
||||
UpdatedAt: shr.UpdatedAt.UnixMilli(),
|
||||
})
|
||||
}
|
||||
envRes.Shares = append(envRes.Shares, envShr)
|
||||
}
|
||||
out = append(out, es)
|
||||
fes, err := str.FindFrontendsForEnvironment(env.Id, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding frontends for environment '%v': %v", env.ZId, err)
|
||||
return metadata.NewOverviewInternalServerError()
|
||||
}
|
||||
for _, fe := range fes {
|
||||
envFe := &rest_model_zrok.Frontend{
|
||||
ID: int64(fe.Id),
|
||||
ZID: fe.ZId,
|
||||
CreatedAt: fe.CreatedAt.UnixMilli(),
|
||||
UpdatedAt: fe.UpdatedAt.UnixMilli(),
|
||||
}
|
||||
if fe.PrivateShareId != nil {
|
||||
feShr, err := str.GetShare(*fe.PrivateShareId, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error getting share for frontend '%v': %v", fe.ZId, err)
|
||||
return metadata.NewOverviewInternalServerError()
|
||||
}
|
||||
envFe.ShrToken = feShr.Token
|
||||
}
|
||||
envRes.Frontends = append(envRes.Frontends, envFe)
|
||||
}
|
||||
ovr.Environments = append(ovr.Environments, envRes)
|
||||
}
|
||||
return metadata.NewOverviewOK().WithPayload(out)
|
||||
return metadata.NewOverviewOK().WithPayload(ovr)
|
||||
}
|
||||
|
||||
func (h *overviewHandler) isAccountLimited(principal *rest_model_zrok.Principal, trx *sqlx.Tx) (bool, error) {
|
||||
var alj *store.AccountLimitJournal
|
||||
aljEmpty, err := str.IsAccountLimitJournalEmpty(int(principal.ID), trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !aljEmpty {
|
||||
alj, err = str.FindLatestAccountLimitJournal(int(principal.ID), trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return alj != nil && alj.Action == store.LimitAction, nil
|
||||
}
|
||||
|
||||
type sharesLimitedMap struct {
|
||||
v map[int]struct{}
|
||||
}
|
||||
|
||||
func newSharesLimitedMap(shrs []*store.Share, trx *sqlx.Tx) (*sharesLimitedMap, error) {
|
||||
var shrIds []int
|
||||
for i := range shrs {
|
||||
shrIds = append(shrIds, shrs[i].Id)
|
||||
}
|
||||
shrsLimited, err := str.FindSelectedLatestShareLimitjournal(shrIds, trx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
slm := &sharesLimitedMap{v: make(map[int]struct{})}
|
||||
for i := range shrsLimited {
|
||||
if shrsLimited[i].Action == store.LimitAction {
|
||||
slm.v[shrsLimited[i].ShareId] = struct{}{}
|
||||
}
|
||||
}
|
||||
return slm, nil
|
||||
}
|
||||
|
||||
func (m *sharesLimitedMap) isLimited(shr *store.Share) bool {
|
||||
_, limited := m.v[shr.Id]
|
||||
return limited
|
||||
}
|
||||
|
||||
type environmentsLimitedMap struct {
|
||||
v map[int]struct{}
|
||||
}
|
||||
|
||||
func newEnvironmentsLimitedMap(envs []*store.Environment, trx *sqlx.Tx) (*environmentsLimitedMap, error) {
|
||||
var envIds []int
|
||||
for i := range envs {
|
||||
envIds = append(envIds, envs[i].Id)
|
||||
}
|
||||
envsLimited, err := str.FindSelectedLatestEnvironmentLimitJournal(envIds, trx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elm := &environmentsLimitedMap{v: make(map[int]struct{})}
|
||||
for i := range envsLimited {
|
||||
if envsLimited[i].Action == store.LimitAction {
|
||||
elm.v[envsLimited[i].EnvironmentId] = struct{}{}
|
||||
}
|
||||
}
|
||||
return elm, nil
|
||||
}
|
||||
|
||||
func (m *environmentsLimitedMap) isLimited(env *store.Environment) bool {
|
||||
_, limited := m.v[env.Id]
|
||||
return limited
|
||||
}
|
||||
|
@ -2,16 +2,21 @@ package controller
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/account"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type registerHandler struct{}
|
||||
type registerHandler struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func newRegisterHandler() *registerHandler {
|
||||
return ®isterHandler{}
|
||||
func newRegisterHandler(cfg *config.Config) *registerHandler {
|
||||
return ®isterHandler{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
func (h *registerHandler) Handle(params account.RegisterParams) middleware.Responder {
|
||||
if params.Body == nil || params.Body.Token == "" || params.Body.Password == "" {
|
||||
@ -38,6 +43,12 @@ func (h *registerHandler) Handle(params account.RegisterParams) middleware.Respo
|
||||
logrus.Errorf("error creating token for request '%v' (%v): %v", params.Body.Token, ar.Email, err)
|
||||
return account.NewRegisterInternalServerError()
|
||||
}
|
||||
|
||||
if err := validatePassword(h.cfg, params.Body.Password); err != nil {
|
||||
logrus.Errorf("password not valid for request '%v', (%v): %v", params.Body.Token, ar.Email, err)
|
||||
return account.NewRegisterUnprocessableEntity().WithPayload(rest_model_zrok.ErrorMessage(err.Error()))
|
||||
}
|
||||
|
||||
hpwd, err := hashPassword(params.Body.Password)
|
||||
if err != nil {
|
||||
logrus.Errorf("error hashing password for request '%v' (%v): %v", params.Body.Token, ar.Email, err)
|
||||
|
@ -2,14 +2,20 @@ package controller
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/openziti/zrok/controller/config"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/account"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type resetPasswordHandler struct{}
|
||||
type resetPasswordHandler struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
func newResetPasswordHandler() *resetPasswordHandler {
|
||||
return &resetPasswordHandler{}
|
||||
func newResetPasswordHandler(cfg *config.Config) *resetPasswordHandler {
|
||||
return &resetPasswordHandler{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (handler *resetPasswordHandler) Handle(params account.ResetPasswordParams) middleware.Responder {
|
||||
@ -37,6 +43,16 @@ func (handler *resetPasswordHandler) Handle(params account.ResetPasswordParams)
|
||||
logrus.Errorf("error finding account for '%v': %v", params.Body.Token, err)
|
||||
return account.NewResetPasswordNotFound()
|
||||
}
|
||||
if a.Deleted {
|
||||
logrus.Errorf("account '%v' for '%v' deleted", a.Email, a.Token)
|
||||
return account.NewResetPasswordNotFound()
|
||||
}
|
||||
|
||||
if err := validatePassword(handler.cfg, params.Body.Password); err != nil {
|
||||
logrus.Errorf("password not valid for request '%v', (%v): %v", params.Body.Token, a.Email, err)
|
||||
return account.NewResetPasswordUnprocessableEntity().WithPayload(rest_model_zrok.ErrorMessage(err.Error()))
|
||||
}
|
||||
|
||||
hpwd, err := hashPassword(params.Body.Password)
|
||||
if err != nil {
|
||||
logrus.Errorf("error hashing password for '%v' (%v): %v", params.Body.Token, a.Email, err)
|
||||
|
@ -42,7 +42,7 @@ func (handler *resetPasswordRequestHandler) Handle(params account.ResetPasswordR
|
||||
|
||||
a, err := str.FindAccountWithEmail(params.Body.EmailAddress, tx)
|
||||
if err != nil {
|
||||
logrus.Infof("no account found for '%v': %v", params.Body.EmailAddress, err)
|
||||
logrus.Errorf("no account found for '%v': %v", params.Body.EmailAddress, err)
|
||||
return account.NewResetPasswordRequestInternalServerError()
|
||||
}
|
||||
|
||||
|
@ -4,33 +4,32 @@ import (
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/share"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type shareHandler struct {
|
||||
cfg *LimitsConfig
|
||||
}
|
||||
type shareHandler struct{}
|
||||
|
||||
func newShareHandler(cfg *LimitsConfig) *shareHandler {
|
||||
return &shareHandler{cfg: cfg}
|
||||
func newShareHandler() *shareHandler {
|
||||
return &shareHandler{}
|
||||
}
|
||||
|
||||
func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zrok.Principal) middleware.Responder {
|
||||
logrus.Infof("handling")
|
||||
logrus.Info("handling")
|
||||
|
||||
tx, err := str.Begin()
|
||||
trx, err := str.Begin()
|
||||
if err != nil {
|
||||
logrus.Errorf("error starting transaction: %v", err)
|
||||
return share.NewShareInternalServerError()
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
defer func() { _ = trx.Rollback() }()
|
||||
|
||||
envZId := params.Body.EnvZID
|
||||
envId := 0
|
||||
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), tx)
|
||||
envs, err := str.FindEnvironmentsForAccount(int(principal.ID), trx)
|
||||
if err == nil {
|
||||
found := false
|
||||
for _, env := range envs {
|
||||
@ -50,12 +49,12 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr
|
||||
return share.NewShareInternalServerError()
|
||||
}
|
||||
|
||||
if err := h.checkLimits(principal, envs, tx); err != nil {
|
||||
if err := h.checkLimits(envId, principal, trx); err != nil {
|
||||
logrus.Errorf("limits error: %v", err)
|
||||
return share.NewShareUnauthorized()
|
||||
}
|
||||
|
||||
edge, err := edgeClient()
|
||||
edge, err := zrokEdgeSdk.Client(cfg.Ziti)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return share.NewShareInternalServerError()
|
||||
@ -78,7 +77,7 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr
|
||||
var frontendZIds []string
|
||||
var frontendTemplates []string
|
||||
for _, frontendSelection := range params.Body.FrontendSelection {
|
||||
sfe, err := str.FindFrontendPubliclyNamed(frontendSelection, tx)
|
||||
sfe, err := str.FindFrontendPubliclyNamed(frontendSelection, trx)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return share.NewShareNotFound()
|
||||
@ -96,6 +95,7 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr
|
||||
}
|
||||
|
||||
case "private":
|
||||
logrus.Info("doing private")
|
||||
shrZId, frontendEndpoints, err = newPrivateResourceAllocator().allocate(envZId, shrToken, params, edge)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
@ -118,19 +118,22 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr
|
||||
BackendProxyEndpoint: ¶ms.Body.BackendProxyEndpoint,
|
||||
Reserved: reserved,
|
||||
}
|
||||
if len(params.Body.FrontendSelection) > 0 {
|
||||
sshr.FrontendSelection = ¶ms.Body.FrontendSelection[0]
|
||||
}
|
||||
if len(frontendEndpoints) > 0 {
|
||||
sshr.FrontendEndpoint = &frontendEndpoints[0]
|
||||
} else if sshr.ShareMode == "private" {
|
||||
sshr.FrontendEndpoint = &sshr.ShareMode
|
||||
}
|
||||
|
||||
sid, err := str.CreateShare(envId, sshr, tx)
|
||||
sid, err := str.CreateShare(envId, sshr, trx)
|
||||
if err != nil {
|
||||
logrus.Errorf("error creating share record: %v", err)
|
||||
return share.NewShareInternalServerError()
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
if err := trx.Commit(); err != nil {
|
||||
logrus.Errorf("error committing share record: %v", err)
|
||||
return share.NewShareInternalServerError()
|
||||
}
|
||||
@ -142,17 +145,15 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr
|
||||
})
|
||||
}
|
||||
|
||||
func (h *shareHandler) checkLimits(principal *rest_model_zrok.Principal, envs []*store.Environment, tx *sqlx.Tx) error {
|
||||
if !principal.Limitless && h.cfg.Shares > Unlimited {
|
||||
total := 0
|
||||
for i := range envs {
|
||||
shrs, err := str.FindSharesForEnvironment(envs[i].Id, tx)
|
||||
func (h *shareHandler) checkLimits(envId int, principal *rest_model_zrok.Principal, trx *sqlx.Tx) error {
|
||||
if !principal.Limitless {
|
||||
if limitsAgent != nil {
|
||||
ok, err := limitsAgent.CanCreateShare(int(principal.ID), envId, trx)
|
||||
if err != nil {
|
||||
return errors.Errorf("unable to find shares for environment '%v': %v", envs[i].ZId, err)
|
||||
return errors.Wrapf(err, "error checking share limits for '%v'", principal.Email)
|
||||
}
|
||||
total += len(shrs)
|
||||
if total+1 > h.cfg.Shares {
|
||||
return errors.Errorf("would exceed shares limit of %d for '%v'", h.cfg.Shares, principal.Email)
|
||||
if !ok {
|
||||
return errors.Errorf("share limit check failed for '%v'", principal.Email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,12 +42,15 @@ func (h *shareDetailHandler) Handle(params metadata.GetShareDetailParams, princi
|
||||
logrus.Errorf("environment not matched for share '%v' for account '%v'", params.ShrToken, principal.Email)
|
||||
return metadata.NewGetShareDetailNotFound()
|
||||
}
|
||||
var sparkData map[string][]int64
|
||||
if cfg.Influx != nil {
|
||||
sparkData, err = sparkDataForShares([]*store.Share{shr})
|
||||
sparkRx := make(map[string][]int64)
|
||||
sparkTx := make(map[string][]int64)
|
||||
if cfg.Metrics != nil && cfg.Metrics.Influx != nil {
|
||||
sparkRx, sparkTx, err = sparkDataForShares([]*store.Share{shr})
|
||||
if err != nil {
|
||||
logrus.Errorf("error querying spark data for share: %v", err)
|
||||
}
|
||||
} else {
|
||||
logrus.Debug("skipping spark data; no influx configuration")
|
||||
}
|
||||
feEndpoint := ""
|
||||
if shr.FrontendEndpoint != nil {
|
||||
@ -61,6 +64,10 @@ func (h *shareDetailHandler) Handle(params metadata.GetShareDetailParams, princi
|
||||
if shr.BackendProxyEndpoint != nil {
|
||||
beProxyEndpoint = *shr.BackendProxyEndpoint
|
||||
}
|
||||
var sparkData []*rest_model_zrok.SparkDataSample
|
||||
for i := 0; i < len(sparkRx[shr.Token]) && i < len(sparkTx[shr.Token]); i++ {
|
||||
sparkData = append(sparkData, &rest_model_zrok.SparkDataSample{Rx: float64(sparkRx[shr.Token][i]), Tx: float64(sparkTx[shr.Token][i])})
|
||||
}
|
||||
return metadata.NewGetShareDetailOK().WithPayload(&rest_model_zrok.Share{
|
||||
Token: shr.Token,
|
||||
ZID: shr.ZId,
|
||||
@ -70,7 +77,7 @@ func (h *shareDetailHandler) Handle(params metadata.GetShareDetailParams, princi
|
||||
FrontendEndpoint: feEndpoint,
|
||||
BackendProxyEndpoint: beProxyEndpoint,
|
||||
Reserved: shr.Reserved,
|
||||
Metrics: sparkData[shr.Token],
|
||||
Activity: sparkData,
|
||||
CreatedAt: shr.CreatedAt.UnixMilli(),
|
||||
UpdatedAt: shr.UpdatedAt.UnixMilli(),
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/openziti/edge/rest_management_api_client"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/openziti/zrok/model"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/share"
|
||||
|
@ -1,7 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/openziti/edge/rest_management_api_client"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/openziti/zrok/model"
|
||||
"github.com/openziti/zrok/rest_server_zrok/operations/share"
|
||||
|
@ -4,55 +4,114 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/openziti/zrok/controller/store"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func sparkDataForShares(shrs []*store.Share) (map[string][]int64, error) {
|
||||
out := make(map[string][]int64)
|
||||
func sparkDataForEnvironments(envs []*store.Environment) (rx, tx map[int][]int64, err error) {
|
||||
rx = make(map[int][]int64)
|
||||
tx = make(map[int][]int64)
|
||||
if len(envs) > 0 {
|
||||
qapi := idb.QueryAPI(cfg.Metrics.Influx.Org)
|
||||
|
||||
if len(shrs) > 0 {
|
||||
qapi := idb.QueryAPI(cfg.Influx.Org)
|
||||
envFilter := "|> filter(fn: (r) =>"
|
||||
for i, env := range envs {
|
||||
if i > 0 {
|
||||
envFilter += " or"
|
||||
}
|
||||
envFilter += fmt.Sprintf(" r[\"envId\"] == \"%d\"", env.Id)
|
||||
}
|
||||
envFilter += ")"
|
||||
query := fmt.Sprintf("from(bucket: \"%v\")\n", cfg.Metrics.Influx.Bucket) +
|
||||
"|> range(start: -5m)\n" +
|
||||
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")\n" +
|
||||
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
||||
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
||||
envFilter +
|
||||
"|> drop(columns: [\"share\", \"acctId\"])\n" +
|
||||
"|> aggregateWindow(every: 10s, fn: sum, createEmpty: true)\n"
|
||||
|
||||
result, err := qapi.Query(context.Background(), sparkFluxQuery(shrs))
|
||||
result, err := qapi.Query(context.Background(), query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for result.Next() {
|
||||
combinedRate := int64(0)
|
||||
readRate := result.Record().ValueByKey("bytesRead")
|
||||
if readRate != nil {
|
||||
combinedRate += readRate.(int64)
|
||||
envIdS := result.Record().ValueByKey("envId").(string)
|
||||
envId, err := strconv.ParseInt(envIdS, 10, 32)
|
||||
if err != nil {
|
||||
logrus.Errorf("error parsing '%v': %v", envIdS, err)
|
||||
continue
|
||||
}
|
||||
writeRate := result.Record().ValueByKey("bytesWritten")
|
||||
if writeRate != nil {
|
||||
combinedRate += writeRate.(int64)
|
||||
switch result.Record().Field() {
|
||||
case "rx":
|
||||
rxV := int64(0)
|
||||
if v, ok := result.Record().Value().(int64); ok {
|
||||
rxV = v
|
||||
}
|
||||
rxData := append(rx[int(envId)], rxV)
|
||||
rx[int(envId)] = rxData
|
||||
|
||||
case "tx":
|
||||
txV := int64(0)
|
||||
if v, ok := result.Record().Value().(int64); ok {
|
||||
txV = v
|
||||
}
|
||||
txData := append(tx[int(envId)], txV)
|
||||
tx[int(envId)] = txData
|
||||
}
|
||||
shrToken := result.Record().ValueByKey("share").(string)
|
||||
shrMetrics := out[shrToken]
|
||||
shrMetrics = append(shrMetrics, combinedRate)
|
||||
out[shrToken] = shrMetrics
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
return rx, tx, nil
|
||||
}
|
||||
|
||||
func sparkFluxQuery(shrs []*store.Share) string {
|
||||
shrFilter := "|> filter(fn: (r) =>"
|
||||
for i, shr := range shrs {
|
||||
if i > 0 {
|
||||
shrFilter += " or"
|
||||
func sparkDataForShares(shrs []*store.Share) (rx, tx map[string][]int64, err error) {
|
||||
rx = make(map[string][]int64)
|
||||
tx = make(map[string][]int64)
|
||||
if len(shrs) > 0 {
|
||||
qapi := idb.QueryAPI(cfg.Metrics.Influx.Org)
|
||||
|
||||
shrFilter := "|> filter(fn: (r) =>"
|
||||
for i, shr := range shrs {
|
||||
if i > 0 {
|
||||
shrFilter += " or"
|
||||
}
|
||||
shrFilter += fmt.Sprintf(" r[\"share\"] == \"%v\"", shr.Token)
|
||||
}
|
||||
shrFilter += ")"
|
||||
query := fmt.Sprintf("from(bucket: \"%v\")\n", cfg.Metrics.Influx.Bucket) +
|
||||
"|> range(start: -5m)\n" +
|
||||
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")\n" +
|
||||
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
||||
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
||||
shrFilter +
|
||||
"|> aggregateWindow(every: 10s, fn: sum, createEmpty: true)\n"
|
||||
|
||||
result, err := qapi.Query(context.Background(), query)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for result.Next() {
|
||||
shrToken := result.Record().ValueByKey("share").(string)
|
||||
switch result.Record().Field() {
|
||||
case "rx":
|
||||
rxV := int64(0)
|
||||
if v, ok := result.Record().Value().(int64); ok {
|
||||
rxV = v
|
||||
}
|
||||
rxData := append(rx[shrToken], rxV)
|
||||
rx[shrToken] = rxData
|
||||
|
||||
case "tx":
|
||||
txV := int64(0)
|
||||
if v, ok := result.Record().Value().(int64); ok {
|
||||
txV = v
|
||||
}
|
||||
txData := append(tx[shrToken], txV)
|
||||
tx[shrToken] = txData
|
||||
}
|
||||
}
|
||||
shrFilter += fmt.Sprintf(" r[\"share\"] == \"%v\"", shr.Token)
|
||||
}
|
||||
shrFilter += ")"
|
||||
query := "read = from(bucket: \"zrok\")" +
|
||||
"|> range(start: -5m)" +
|
||||
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")" +
|
||||
"|> filter(fn: (r) => r[\"_field\"] == \"bytesRead\" or r[\"_field\"] == \"bytesWritten\")" +
|
||||
"|> filter(fn: (r) => r[\"namespace\"] == \"frontend\")" +
|
||||
shrFilter +
|
||||
"|> aggregateWindow(every: 5s, fn: sum, createEmpty: true)\n" +
|
||||
"|> pivot(rowKey:[\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")" +
|
||||
"|> yield(name: \"last\")"
|
||||
return query
|
||||
return rx, tx, nil
|
||||
}
|
||||
|
@ -3,8 +3,9 @@ package controller
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/openziti/edge/rest_management_api_client"
|
||||
"github.com/openziti/edge/rest_management_api_client/config"
|
||||
"github.com/openziti/edge-api/rest_management_api_client"
|
||||
"github.com/openziti/edge-api/rest_management_api_client/config"
|
||||
"github.com/openziti/zrok/controller/zrokEdgeSdk"
|
||||
"github.com/openziti/zrok/model"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -23,7 +24,7 @@ func controllerStartup() error {
|
||||
func inspectZiti() error {
|
||||
logrus.Infof("inspecting ziti controller configuration")
|
||||
|
||||
edge, err := edgeClient()
|
||||
edge, err := zrokEdgeSdk.Client(cfg.Ziti)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting ziti edge client")
|
||||
}
|
||||
|
@ -12,9 +12,10 @@ type Account struct {
|
||||
Password string
|
||||
Token string
|
||||
Limitless bool
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
func (self *Store) CreateAccount(a *Account, tx *sqlx.Tx) (int, error) {
|
||||
func (str *Store) CreateAccount(a *Account, tx *sqlx.Tx) (int, error) {
|
||||
stmt, err := tx.Prepare("insert into accounts (email, salt, password, token, limitless) values ($1, $2, $3, $4, $5) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing accounts insert statement")
|
||||
@ -26,7 +27,7 @@ func (self *Store) CreateAccount(a *Account, tx *sqlx.Tx) (int, error) {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (self *Store) GetAccount(id int, tx *sqlx.Tx) (*Account, error) {
|
||||
func (str *Store) GetAccount(id int, tx *sqlx.Tx) (*Account, error) {
|
||||
a := &Account{}
|
||||
if err := tx.QueryRowx("select * from accounts where id = $1", id).StructScan(a); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting account by id")
|
||||
@ -34,23 +35,31 @@ func (self *Store) GetAccount(id int, tx *sqlx.Tx) (*Account, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindAccountWithEmail(email string, tx *sqlx.Tx) (*Account, error) {
|
||||
func (str *Store) FindAccountWithEmail(email string, tx *sqlx.Tx) (*Account, error) {
|
||||
a := &Account{}
|
||||
if err := tx.QueryRowx("select * from accounts where email = $1", email).StructScan(a); err != nil {
|
||||
if err := tx.QueryRowx("select * from accounts where email = $1 and not deleted", email).StructScan(a); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting account by email")
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindAccountWithToken(token string, tx *sqlx.Tx) (*Account, error) {
|
||||
func (str *Store) FindAccountWithEmailAndDeleted(email string, tx *sqlx.Tx) (*Account, error) {
|
||||
a := &Account{}
|
||||
if err := tx.QueryRowx("select * from accounts where token = $1", token).StructScan(a); err != nil {
|
||||
if err := tx.QueryRowx("select * from accounts where email = $1", email).StructScan(a); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting acount by email")
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindAccountWithToken(token string, tx *sqlx.Tx) (*Account, error) {
|
||||
a := &Account{}
|
||||
if err := tx.QueryRowx("select * from accounts where token = $1 and not deleted", token).StructScan(a); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting account by token")
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (self *Store) UpdateAccount(a *Account, tx *sqlx.Tx) (int, error) {
|
||||
func (str *Store) UpdateAccount(a *Account, tx *sqlx.Tx) (int, error) {
|
||||
stmt, err := tx.Prepare("update accounts set email=$1, salt=$2, password=$3, token=$4, limitless=$5 where id = $6")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing accounts update statement")
|
||||
|
65
controller/store/accountLimitJournal.go
Normal file
65
controller/store/accountLimitJournal.go
Normal file
@ -0,0 +1,65 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type AccountLimitJournal struct {
|
||||
Model
|
||||
AccountId int
|
||||
RxBytes int64
|
||||
TxBytes int64
|
||||
Action LimitJournalAction
|
||||
}
|
||||
|
||||
func (str *Store) CreateAccountLimitJournal(j *AccountLimitJournal, trx *sqlx.Tx) (int, error) {
|
||||
stmt, err := trx.Prepare("insert into account_limit_journal (account_id, rx_bytes, tx_bytes, action) values ($1, $2, $3, $4) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing account_limit_journal insert statement")
|
||||
}
|
||||
var id int
|
||||
if err := stmt.QueryRow(j.AccountId, j.RxBytes, j.TxBytes, j.Action).Scan(&id); err != nil {
|
||||
return 0, errors.Wrap(err, "error executing account_limit_journal insert statement")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (str *Store) IsAccountLimitJournalEmpty(acctId int, trx *sqlx.Tx) (bool, error) {
|
||||
count := 0
|
||||
if err := trx.QueryRowx("select count(0) from account_limit_journal where account_id = $1", acctId).Scan(&count); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count == 0, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindLatestAccountLimitJournal(acctId int, trx *sqlx.Tx) (*AccountLimitJournal, error) {
|
||||
j := &AccountLimitJournal{}
|
||||
if err := trx.QueryRowx("select * from account_limit_journal where account_id = $1 order by id desc limit 1", acctId).StructScan(j); err != nil {
|
||||
return nil, errors.Wrap(err, "error finding account_limit_journal by account_id")
|
||||
}
|
||||
return j, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindAllLatestAccountLimitJournal(trx *sqlx.Tx) ([]*AccountLimitJournal, error) {
|
||||
rows, err := trx.Queryx("select id, account_id, rx_bytes, tx_bytes, action, created_at, updated_at from account_limit_journal where id in (select max(id) as id from account_limit_journal group by account_id)")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting all latest account_limit_journal")
|
||||
}
|
||||
var aljs []*AccountLimitJournal
|
||||
for rows.Next() {
|
||||
alj := &AccountLimitJournal{}
|
||||
if err := rows.StructScan(alj); err != nil {
|
||||
return nil, errors.Wrap(err, "error scanning account_limit_journal")
|
||||
}
|
||||
aljs = append(aljs, alj)
|
||||
}
|
||||
return aljs, nil
|
||||
}
|
||||
|
||||
func (str *Store) DeleteAccountLimitJournalForAccount(acctId int, trx *sqlx.Tx) error {
|
||||
if _, err := trx.Exec("delete from account_limit_journal where account_id = $1", acctId); err != nil {
|
||||
return errors.Wrapf(err, "error deleting account_limit journal for '#%d'", acctId)
|
||||
}
|
||||
return nil
|
||||
}
|
79
controller/store/accountLimitJournal_test.go
Normal file
79
controller/store/accountLimitJournal_test.go
Normal file
@ -0,0 +1,79 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAccountLimitJournal(t *testing.T) {
|
||||
str, err := Open(&Config{Path: ":memory:", Type: "sqlite3"})
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, str)
|
||||
|
||||
trx, err := str.Begin()
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, trx)
|
||||
|
||||
aljEmpty, err := str.IsAccountLimitJournalEmpty(1, trx)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, aljEmpty)
|
||||
|
||||
acctId, err := str.CreateAccount(&Account{Email: "nobody@nowehere.com", Salt: "salt", Password: "password", Token: "token", Limitless: false, Deleted: false}, trx)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId, RxBytes: 1024, TxBytes: 2048, Action: WarningAction}, trx)
|
||||
assert.Nil(t, err)
|
||||
|
||||
aljEmpty, err = str.IsAccountLimitJournalEmpty(acctId, trx)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, aljEmpty)
|
||||
|
||||
latestAlj, err := str.FindLatestAccountLimitJournal(acctId, trx)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, latestAlj)
|
||||
assert.Equal(t, int64(1024), latestAlj.RxBytes)
|
||||
assert.Equal(t, int64(2048), latestAlj.TxBytes)
|
||||
assert.Equal(t, WarningAction, latestAlj.Action)
|
||||
|
||||
_, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId, RxBytes: 2048, TxBytes: 4096, Action: LimitAction}, trx)
|
||||
assert.Nil(t, err)
|
||||
|
||||
latestAlj, err = str.FindLatestAccountLimitJournal(acctId, trx)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, latestAlj)
|
||||
assert.Equal(t, int64(2048), latestAlj.RxBytes)
|
||||
assert.Equal(t, int64(4096), latestAlj.TxBytes)
|
||||
assert.Equal(t, LimitAction, latestAlj.Action)
|
||||
}
|
||||
|
||||
func TestFindAllLatestAccountLimitJournal(t *testing.T) {
|
||||
str, err := Open(&Config{Path: ":memory:", Type: "sqlite3"})
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, str)
|
||||
|
||||
trx, err := str.Begin()
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, trx)
|
||||
|
||||
acctId1, err := str.CreateAccount(&Account{Email: "nobody@nowehere.com", Salt: "salt1", Password: "password1", Token: "token1", Limitless: false, Deleted: false}, trx)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: WarningAction}, trx)
|
||||
assert.Nil(t, err)
|
||||
_, err = str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: ClearAction}, trx)
|
||||
assert.Nil(t, err)
|
||||
aljId13, err := str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId1, RxBytes: 2048, TxBytes: 4096, Action: LimitAction}, trx)
|
||||
assert.Nil(t, err)
|
||||
|
||||
acctId2, err := str.CreateAccount(&Account{Email: "someone@somewhere.com", Salt: "salt2", Password: "password2", Token: "token2", Limitless: false, Deleted: false}, trx)
|
||||
assert.Nil(t, err)
|
||||
|
||||
aljId21, err := str.CreateAccountLimitJournal(&AccountLimitJournal{AccountId: acctId2, RxBytes: 2048, TxBytes: 4096, Action: WarningAction}, trx)
|
||||
assert.Nil(t, err)
|
||||
|
||||
aljs, err := str.FindAllLatestAccountLimitJournal(trx)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(aljs))
|
||||
assert.Equal(t, aljId13, aljs[0].Id)
|
||||
assert.Equal(t, aljId21, aljs[1].Id)
|
||||
}
|
@ -14,9 +14,10 @@ type AccountRequest struct {
|
||||
Token string
|
||||
Email string
|
||||
SourceAddress string
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
func (self *Store) CreateAccountRequest(ar *AccountRequest, tx *sqlx.Tx) (int, error) {
|
||||
func (str *Store) CreateAccountRequest(ar *AccountRequest, tx *sqlx.Tx) (int, error) {
|
||||
stmt, err := tx.Prepare("insert into account_requests (token, email, source_address) values ($1, $2, $3) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing account_requests insert statement")
|
||||
@ -28,7 +29,7 @@ func (self *Store) CreateAccountRequest(ar *AccountRequest, tx *sqlx.Tx) (int, e
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (self *Store) GetAccountRequest(id int, tx *sqlx.Tx) (*AccountRequest, error) {
|
||||
func (str *Store) GetAccountRequest(id int, tx *sqlx.Tx) (*AccountRequest, error) {
|
||||
ar := &AccountRequest{}
|
||||
if err := tx.QueryRowx("select * from account_requests where id = $1", id).StructScan(ar); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting account_request by id")
|
||||
@ -36,25 +37,25 @@ func (self *Store) GetAccountRequest(id int, tx *sqlx.Tx) (*AccountRequest, erro
|
||||
return ar, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindAccountRequestWithToken(token string, tx *sqlx.Tx) (*AccountRequest, error) {
|
||||
func (str *Store) FindAccountRequestWithToken(token string, tx *sqlx.Tx) (*AccountRequest, error) {
|
||||
ar := &AccountRequest{}
|
||||
if err := tx.QueryRowx("select * from account_requests where token = $1", token).StructScan(ar); err != nil {
|
||||
if err := tx.QueryRowx("select * from account_requests where token = $1 and not deleted", token).StructScan(ar); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting account_request by token")
|
||||
}
|
||||
return ar, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindExpiredAccountRequests(before time.Time, limit int, tx *sqlx.Tx) ([]*AccountRequest, error) {
|
||||
func (str *Store) FindExpiredAccountRequests(before time.Time, limit int, tx *sqlx.Tx) ([]*AccountRequest, error) {
|
||||
var sql string
|
||||
switch self.cfg.Type {
|
||||
switch str.cfg.Type {
|
||||
case "postgres":
|
||||
sql = "select * from account_requests where created_at < $1 limit %d for update"
|
||||
sql = "select * from account_requests where created_at < $1 and not deleted limit %d for update"
|
||||
|
||||
case "sqlite3":
|
||||
sql = "select * from account_requests where created_at < $1 limit %d"
|
||||
sql = "select * from account_requests where created_at < $1 and not deleted limit %d"
|
||||
|
||||
default:
|
||||
return nil, errors.Errorf("unknown database type '%v'", self.cfg.Type)
|
||||
return nil, errors.Errorf("unknown database type '%v'", str.cfg.Type)
|
||||
}
|
||||
|
||||
rows, err := tx.Queryx(fmt.Sprintf(sql, limit), before)
|
||||
@ -72,16 +73,16 @@ func (self *Store) FindExpiredAccountRequests(before time.Time, limit int, tx *s
|
||||
return ars, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindAccountRequestWithEmail(email string, tx *sqlx.Tx) (*AccountRequest, error) {
|
||||
func (str *Store) FindAccountRequestWithEmail(email string, tx *sqlx.Tx) (*AccountRequest, error) {
|
||||
ar := &AccountRequest{}
|
||||
if err := tx.QueryRowx("select * from account_requests where email = $1", email).StructScan(ar); err != nil {
|
||||
if err := tx.QueryRowx("select * from account_requests where email = $1 and not deleted", email).StructScan(ar); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting account_request by email")
|
||||
}
|
||||
return ar, nil
|
||||
}
|
||||
|
||||
func (self *Store) DeleteAccountRequest(id int, tx *sqlx.Tx) error {
|
||||
stmt, err := tx.Prepare("delete from account_requests where id = $1")
|
||||
func (str *Store) DeleteAccountRequest(id int, tx *sqlx.Tx) error {
|
||||
stmt, err := tx.Prepare("update account_requests set deleted = true, updated_at = current_timestamp where id = $1")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing account_requests delete statement")
|
||||
}
|
||||
@ -92,7 +93,7 @@ func (self *Store) DeleteAccountRequest(id int, tx *sqlx.Tx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Store) DeleteMultipleAccountRequests(ids []int, tx *sqlx.Tx) error {
|
||||
func (str *Store) DeleteMultipleAccountRequests(ids []int, tx *sqlx.Tx) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -105,7 +106,7 @@ func (self *Store) DeleteMultipleAccountRequests(ids []int, tx *sqlx.Tx) error {
|
||||
indexes[i] = fmt.Sprintf("$%d", i+1)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(fmt.Sprintf("delete from account_requests where id in (%s)", strings.Join(indexes, ",")))
|
||||
stmt, err := tx.Prepare(fmt.Sprintf("update account_requests set deleted = true, updated_at = current_timestamp where id in (%s)", strings.Join(indexes, ",")))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing account_requests delete multiple statement")
|
||||
}
|
||||
|
@ -12,9 +12,10 @@ type Environment struct {
|
||||
Host string
|
||||
Address string
|
||||
ZId string
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
func (self *Store) CreateEnvironment(accountId int, i *Environment, tx *sqlx.Tx) (int, error) {
|
||||
func (str *Store) CreateEnvironment(accountId int, i *Environment, tx *sqlx.Tx) (int, error) {
|
||||
stmt, err := tx.Prepare("insert into environments (account_id, description, host, address, z_id) values ($1, $2, $3, $4, $5) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing environments insert statement")
|
||||
@ -26,7 +27,7 @@ func (self *Store) CreateEnvironment(accountId int, i *Environment, tx *sqlx.Tx)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (self *Store) CreateEphemeralEnvironment(i *Environment, tx *sqlx.Tx) (int, error) {
|
||||
func (str *Store) CreateEphemeralEnvironment(i *Environment, tx *sqlx.Tx) (int, error) {
|
||||
stmt, err := tx.Prepare("insert into environments (description, host, address, z_id) values ($1, $2, $3, $4) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing environments (ephemeral) insert statement")
|
||||
@ -38,7 +39,7 @@ func (self *Store) CreateEphemeralEnvironment(i *Environment, tx *sqlx.Tx) (int,
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (self *Store) GetEnvironment(id int, tx *sqlx.Tx) (*Environment, error) {
|
||||
func (str *Store) GetEnvironment(id int, tx *sqlx.Tx) (*Environment, error) {
|
||||
i := &Environment{}
|
||||
if err := tx.QueryRowx("select * from environments where id = $1", id).StructScan(i); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting environment by id")
|
||||
@ -46,8 +47,8 @@ func (self *Store) GetEnvironment(id int, tx *sqlx.Tx) (*Environment, error) {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindEnvironmentsForAccount(accountId int, tx *sqlx.Tx) ([]*Environment, error) {
|
||||
rows, err := tx.Queryx("select environments.* from environments where account_id = $1", accountId)
|
||||
func (str *Store) FindEnvironmentsForAccount(accountId int, tx *sqlx.Tx) ([]*Environment, error) {
|
||||
rows, err := tx.Queryx("select environments.* from environments where account_id = $1 and not deleted", accountId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting environments by account id")
|
||||
}
|
||||
@ -62,16 +63,16 @@ func (self *Store) FindEnvironmentsForAccount(accountId int, tx *sqlx.Tx) ([]*En
|
||||
return is, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindEnvironmentForAccount(envZId string, accountId int, tx *sqlx.Tx) (*Environment, error) {
|
||||
func (str *Store) FindEnvironmentForAccount(envZId string, accountId int, tx *sqlx.Tx) (*Environment, error) {
|
||||
env := &Environment{}
|
||||
if err := tx.QueryRowx("select environments.* from environments where z_id = $1 and account_id = $2", envZId, accountId).StructScan(env); err != nil {
|
||||
if err := tx.QueryRowx("select environments.* from environments where z_id = $1 and account_id = $2 and not deleted", envZId, accountId).StructScan(env); err != nil {
|
||||
return nil, errors.Wrap(err, "error finding environment by z_id and account_id")
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func (self *Store) DeleteEnvironment(id int, tx *sqlx.Tx) error {
|
||||
stmt, err := tx.Prepare("delete from environments where id = $1")
|
||||
func (str *Store) DeleteEnvironment(id int, tx *sqlx.Tx) error {
|
||||
stmt, err := tx.Prepare("update environments set updated_at = current_timestamp, deleted = true where id = $1")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing environments delete statement")
|
||||
}
|
||||
|
93
controller/store/environmentLimitJournal.go
Normal file
93
controller/store/environmentLimitJournal.go
Normal file
@ -0,0 +1,93 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type EnvironmentLimitJournal struct {
|
||||
Model
|
||||
EnvironmentId int
|
||||
RxBytes int64
|
||||
TxBytes int64
|
||||
Action LimitJournalAction
|
||||
}
|
||||
|
||||
func (str *Store) CreateEnvironmentLimitJournal(j *EnvironmentLimitJournal, trx *sqlx.Tx) (int, error) {
|
||||
stmt, err := trx.Prepare("insert into environment_limit_journal (environment_id, rx_bytes, tx_bytes, action) values ($1, $2, $3, $4) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing environment_limit_journal insert statement")
|
||||
}
|
||||
var id int
|
||||
if err := stmt.QueryRow(j.EnvironmentId, j.RxBytes, j.TxBytes, j.Action).Scan(&id); err != nil {
|
||||
return 0, errors.Wrap(err, "error executing environment_limit_journal insert statement")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (str *Store) IsEnvironmentLimitJournalEmpty(envId int, trx *sqlx.Tx) (bool, error) {
|
||||
count := 0
|
||||
if err := trx.QueryRowx("select count(0) from environment_limit_journal where environment_id = $1", envId).Scan(&count); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count == 0, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindLatestEnvironmentLimitJournal(envId int, trx *sqlx.Tx) (*EnvironmentLimitJournal, error) {
|
||||
j := &EnvironmentLimitJournal{}
|
||||
if err := trx.QueryRowx("select * from environment_limit_journal where environment_id = $1 order by created_at desc limit 1", envId).StructScan(j); err != nil {
|
||||
return nil, errors.Wrap(err, "error finding environment_limit_journal by environment_id")
|
||||
}
|
||||
return j, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindSelectedLatestEnvironmentLimitJournal(envIds []int, trx *sqlx.Tx) ([]*EnvironmentLimitJournal, error) {
|
||||
if len(envIds) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
in := "("
|
||||
for i := range envIds {
|
||||
if i > 0 {
|
||||
in += ", "
|
||||
}
|
||||
in += fmt.Sprintf("%d", envIds[i])
|
||||
}
|
||||
in += ")"
|
||||
rows, err := trx.Queryx("select id, environment_id, rx_bytes, tx_bytes, action, created_at, updated_at from environment_limit_journal where id in (select max(id) as id from environment_limit_journal group by environment_id) and environment_id in " + in)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting all latest environment_limit_journal")
|
||||
}
|
||||
var eljs []*EnvironmentLimitJournal
|
||||
for rows.Next() {
|
||||
elj := &EnvironmentLimitJournal{}
|
||||
if err := rows.StructScan(elj); err != nil {
|
||||
return nil, errors.Wrap(err, "error scanning environment_limit_journal")
|
||||
}
|
||||
eljs = append(eljs, elj)
|
||||
}
|
||||
return eljs, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindAllLatestEnvironmentLimitJournal(trx *sqlx.Tx) ([]*EnvironmentLimitJournal, error) {
|
||||
rows, err := trx.Queryx("select id, environment_id, rx_bytes, tx_bytes, action, created_at, updated_at from environment_limit_journal where id in (select max(id) as id from environment_limit_journal group by environment_id)")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting all latest environment_limit_journal")
|
||||
}
|
||||
var eljs []*EnvironmentLimitJournal
|
||||
for rows.Next() {
|
||||
elj := &EnvironmentLimitJournal{}
|
||||
if err := rows.StructScan(elj); err != nil {
|
||||
return nil, errors.Wrap(err, "error scanning environment_limit_journal")
|
||||
}
|
||||
eljs = append(eljs, elj)
|
||||
}
|
||||
return eljs, nil
|
||||
}
|
||||
|
||||
func (str *Store) DeleteEnvironmentLimitJournalForEnvironment(envId int, trx *sqlx.Tx) error {
|
||||
if _, err := trx.Exec("delete from environment_limit_journal where environment_id = $1", envId); err != nil {
|
||||
return errors.Wrapf(err, "error deleteing environment_limit_journal for '#%d'", envId)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -26,6 +26,7 @@ func TestEphemeralEnvironment(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, env)
|
||||
assert.Nil(t, env.AccountId)
|
||||
assert.False(t, env.Deleted)
|
||||
}
|
||||
|
||||
func TestEnvironment(t *testing.T) {
|
||||
@ -57,4 +58,5 @@ func TestEnvironment(t *testing.T) {
|
||||
assert.NotNil(t, env)
|
||||
assert.NotNil(t, env.AccountId)
|
||||
assert.Equal(t, acctId, *env.AccountId)
|
||||
assert.False(t, env.Deleted)
|
||||
}
|
||||
|
@ -7,21 +7,23 @@ import (
|
||||
|
||||
type Frontend struct {
|
||||
Model
|
||||
EnvironmentId *int
|
||||
Token string
|
||||
ZId string
|
||||
PublicName *string
|
||||
UrlTemplate *string
|
||||
Reserved bool
|
||||
EnvironmentId *int
|
||||
PrivateShareId *int
|
||||
Token string
|
||||
ZId string
|
||||
PublicName *string
|
||||
UrlTemplate *string
|
||||
Reserved bool
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
func (str *Store) CreateFrontend(envId int, f *Frontend, tx *sqlx.Tx) (int, error) {
|
||||
stmt, err := tx.Prepare("insert into frontends (environment_id, token, z_id, public_name, url_template, reserved) values ($1, $2, $3, $4, $5, $6) returning id")
|
||||
stmt, err := tx.Prepare("insert into frontends (environment_id, private_share_id, token, z_id, public_name, url_template, reserved) values ($1, $2, $3, $4, $5, $6, $7) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing frontends insert statement")
|
||||
}
|
||||
var id int
|
||||
if err := stmt.QueryRow(envId, f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved).Scan(&id); err != nil {
|
||||
if err := stmt.QueryRow(envId, f.PrivateShareId, f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved).Scan(&id); err != nil {
|
||||
return 0, errors.Wrap(err, "error executing frontends insert statement")
|
||||
}
|
||||
return id, nil
|
||||
@ -49,7 +51,7 @@ func (str *Store) GetFrontend(id int, tx *sqlx.Tx) (*Frontend, error) {
|
||||
|
||||
func (str *Store) FindFrontendWithToken(token string, tx *sqlx.Tx) (*Frontend, error) {
|
||||
i := &Frontend{}
|
||||
if err := tx.QueryRowx("select frontends.* from frontends where token = $1", token).StructScan(i); err != nil {
|
||||
if err := tx.QueryRowx("select frontends.* from frontends where token = $1 and not deleted", token).StructScan(i); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting frontend by name")
|
||||
}
|
||||
return i, nil
|
||||
@ -57,7 +59,7 @@ func (str *Store) FindFrontendWithToken(token string, tx *sqlx.Tx) (*Frontend, e
|
||||
|
||||
func (str *Store) FindFrontendWithZId(zId string, tx *sqlx.Tx) (*Frontend, error) {
|
||||
i := &Frontend{}
|
||||
if err := tx.QueryRowx("select frontends.* from frontends where z_id = $1", zId).StructScan(i); err != nil {
|
||||
if err := tx.QueryRowx("select frontends.* from frontends where z_id = $1 and not deleted", zId).StructScan(i); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting frontend by ziti id")
|
||||
}
|
||||
return i, nil
|
||||
@ -65,14 +67,14 @@ func (str *Store) FindFrontendWithZId(zId string, tx *sqlx.Tx) (*Frontend, error
|
||||
|
||||
func (str *Store) FindFrontendPubliclyNamed(publicName string, tx *sqlx.Tx) (*Frontend, error) {
|
||||
i := &Frontend{}
|
||||
if err := tx.QueryRowx("select frontends.* from frontends where public_name = $1", publicName).StructScan(i); err != nil {
|
||||
if err := tx.QueryRowx("select frontends.* from frontends where public_name = $1 and not deleted", publicName).StructScan(i); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting frontend by public_name")
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindFrontendsForEnvironment(envId int, tx *sqlx.Tx) ([]*Frontend, error) {
|
||||
rows, err := tx.Queryx("select frontends.* from frontends where environment_id = $1", envId)
|
||||
rows, err := tx.Queryx("select frontends.* from frontends where environment_id = $1 and not deleted", envId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting frontends by environment_id")
|
||||
}
|
||||
@ -88,7 +90,7 @@ func (str *Store) FindFrontendsForEnvironment(envId int, tx *sqlx.Tx) ([]*Fronte
|
||||
}
|
||||
|
||||
func (str *Store) FindPublicFrontends(tx *sqlx.Tx) ([]*Frontend, error) {
|
||||
rows, err := tx.Queryx("select frontends.* from frontends where environment_id is null and reserved = true")
|
||||
rows, err := tx.Queryx("select frontends.* from frontends where environment_id is null and reserved = true and not deleted")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting public frontends")
|
||||
}
|
||||
@ -103,13 +105,29 @@ func (str *Store) FindPublicFrontends(tx *sqlx.Tx) ([]*Frontend, error) {
|
||||
return frontends, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindFrontendsForPrivateShare(shrId int, tx *sqlx.Tx) ([]*Frontend, error) {
|
||||
rows, err := tx.Queryx("select frontends.* from frontends where private_share_id = $1 and not deleted", shrId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting frontends by private_share_id")
|
||||
}
|
||||
var is []*Frontend
|
||||
for rows.Next() {
|
||||
i := &Frontend{}
|
||||
if err := rows.StructScan(i); err != nil {
|
||||
return nil, errors.Wrap(err, "error scanning frontend")
|
||||
}
|
||||
is = append(is, i)
|
||||
}
|
||||
return is, nil
|
||||
}
|
||||
|
||||
func (str *Store) UpdateFrontend(fe *Frontend, tx *sqlx.Tx) error {
|
||||
sql := "update frontends set environment_id = $1, token = $2, z_id = $3, public_name = $4, url_template = $5, reserved = $6, updated_at = current_timestamp where id = $7"
|
||||
sql := "update frontends set environment_id = $1, private_share_id = $2, token = $3, z_id = $4, public_name = $5, url_template = $6, reserved = $7, updated_at = current_timestamp where id = $8"
|
||||
stmt, err := tx.Prepare(sql)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing frontends update statement")
|
||||
}
|
||||
_, err = stmt.Exec(fe.EnvironmentId, fe.Token, fe.ZId, fe.PublicName, fe.UrlTemplate, fe.Reserved, fe.Id)
|
||||
_, err = stmt.Exec(fe.EnvironmentId, fe.PrivateShareId, fe.Token, fe.ZId, fe.PublicName, fe.UrlTemplate, fe.Reserved, fe.Id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error executing frontends update statement")
|
||||
}
|
||||
@ -118,7 +136,7 @@ func (str *Store) UpdateFrontend(fe *Frontend, tx *sqlx.Tx) error {
|
||||
|
||||
func (str *Store) DeleteFrontend(id int, tx *sqlx.Tx) error {
|
||||
|
||||
stmt, err := tx.Prepare("delete from frontends where id = $1")
|
||||
stmt, err := tx.Prepare("update frontends set updated_at = current_timestamp, deleted = true where id = $1")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing frontends delete statement")
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ func TestPublicFrontend(t *testing.T) {
|
||||
assert.NotNil(t, fe)
|
||||
assert.Equal(t, envId, *fe.EnvironmentId)
|
||||
assert.Equal(t, feName, *fe.PublicName)
|
||||
assert.False(t, fe.Deleted)
|
||||
|
||||
fe0, err := str.FindFrontendPubliclyNamed(feName, tx)
|
||||
assert.Nil(t, err)
|
||||
@ -56,6 +57,7 @@ func TestPublicFrontend(t *testing.T) {
|
||||
assert.Nil(t, fe0)
|
||||
|
||||
fe0, err = str.GetFrontend(fe.Id, tx)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, fe0)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, fe0)
|
||||
assert.True(t, fe0.Deleted)
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ import (
|
||||
|
||||
type InviteToken struct {
|
||||
Model
|
||||
Token string
|
||||
Token string
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
func (str *Store) CreateInviteTokens(inviteTokens []*InviteToken, tx *sqlx.Tx) error {
|
||||
@ -31,16 +32,16 @@ func (str *Store) CreateInviteTokens(inviteTokens []*InviteToken, tx *sqlx.Tx) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (str *Store) GetInviteTokenByToken(token string, tx *sqlx.Tx) (*InviteToken, error) {
|
||||
func (str *Store) FindInviteTokenByToken(token string, tx *sqlx.Tx) (*InviteToken, error) {
|
||||
inviteToken := &InviteToken{}
|
||||
if err := tx.QueryRowx("select * from invite_tokens where token = $1", token).StructScan(inviteToken); err != nil {
|
||||
if err := tx.QueryRowx("select * from invite_tokens where token = $1 and not deleted", token).StructScan(inviteToken); err != nil {
|
||||
return nil, errors.Wrap(err, "error getting unused invite_token")
|
||||
}
|
||||
return inviteToken, nil
|
||||
}
|
||||
|
||||
func (str *Store) DeleteInviteToken(id int, tx *sqlx.Tx) error {
|
||||
stmt, err := tx.Prepare("delete from invite_tokens where id = $1")
|
||||
stmt, err := tx.Prepare("update invite_tokens set updated_at = current_timestamp, deleted = true where id = $1")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing invite_tokens delete statement")
|
||||
}
|
||||
|
9
controller/store/model.go
Normal file
9
controller/store/model.go
Normal file
@ -0,0 +1,9 @@
|
||||
package store
|
||||
|
||||
type LimitJournalAction string
|
||||
|
||||
const (
|
||||
LimitAction LimitJournalAction = "limit"
|
||||
WarningAction LimitJournalAction = "warning"
|
||||
ClearAction LimitJournalAction = "clear"
|
||||
)
|
@ -13,9 +13,10 @@ type PasswordResetRequest struct {
|
||||
Model
|
||||
Token string
|
||||
AccountId int
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
func (self *Store) CreatePasswordResetRequest(prr *PasswordResetRequest, tx *sqlx.Tx) (int, error) {
|
||||
func (str *Store) CreatePasswordResetRequest(prr *PasswordResetRequest, tx *sqlx.Tx) (int, error) {
|
||||
stmt, err := tx.Prepare("insert into password_reset_requests (account_id, token) values ($1, $2) ON CONFLICT(account_id) DO UPDATE SET token=$2 returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing password_reset_requests insert statement")
|
||||
@ -27,24 +28,24 @@ func (self *Store) CreatePasswordResetRequest(prr *PasswordResetRequest, tx *sql
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindPasswordResetRequestWithToken(token string, tx *sqlx.Tx) (*PasswordResetRequest, error) {
|
||||
func (str *Store) FindPasswordResetRequestWithToken(token string, tx *sqlx.Tx) (*PasswordResetRequest, error) {
|
||||
prr := &PasswordResetRequest{}
|
||||
if err := tx.QueryRowx("select * from password_reset_requests where token = $1", token).StructScan(prr); err != nil {
|
||||
if err := tx.QueryRowx("select * from password_reset_requests where token = $1 and not deleted", token).StructScan(prr); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting password_reset_requests by token")
|
||||
}
|
||||
return prr, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindExpiredPasswordResetRequests(before time.Time, limit int, tx *sqlx.Tx) ([]*PasswordResetRequest, error) {
|
||||
func (str *Store) FindExpiredPasswordResetRequests(before time.Time, limit int, tx *sqlx.Tx) ([]*PasswordResetRequest, error) {
|
||||
var sql string
|
||||
switch self.cfg.Type {
|
||||
switch str.cfg.Type {
|
||||
case "postgres":
|
||||
sql = "select * from password_reset_requests where created_at < $1 limit %d for update"
|
||||
sql = "select * from password_reset_requests where created_at < $1 and not deleted limit %d for update"
|
||||
|
||||
case "sqlite3":
|
||||
sql = "select * from password_reset_requests where created_at < $1 limit %d"
|
||||
sql = "select * from password_reset_requests where created_at < $1 and not deleted limit %d"
|
||||
default:
|
||||
return nil, errors.Errorf("unknown database type '%v'", self.cfg.Type)
|
||||
return nil, errors.Errorf("unknown database type '%v'", str.cfg.Type)
|
||||
}
|
||||
|
||||
rows, err := tx.Queryx(fmt.Sprintf(sql, limit), before)
|
||||
@ -62,8 +63,8 @@ func (self *Store) FindExpiredPasswordResetRequests(before time.Time, limit int,
|
||||
return prrs, nil
|
||||
}
|
||||
|
||||
func (self *Store) DeletePasswordResetRequest(id int, tx *sqlx.Tx) error {
|
||||
stmt, err := tx.Prepare("delete from password_reset_requests where id = $1")
|
||||
func (str *Store) DeletePasswordResetRequest(id int, tx *sqlx.Tx) error {
|
||||
stmt, err := tx.Prepare("update password_reset_requests set updated_at = current_timestamp, deleted = true where id = $1")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing password_reset_requests delete statement")
|
||||
}
|
||||
@ -74,7 +75,7 @@ func (self *Store) DeletePasswordResetRequest(id int, tx *sqlx.Tx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Store) DeleteMultiplePasswordResetRequests(ids []int, tx *sqlx.Tx) error {
|
||||
func (str *Store) DeleteMultiplePasswordResetRequests(ids []int, tx *sqlx.Tx) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -87,7 +88,7 @@ func (self *Store) DeleteMultiplePasswordResetRequests(ids []int, tx *sqlx.Tx) e
|
||||
indexes[i] = fmt.Sprintf("$%d", i+1)
|
||||
}
|
||||
|
||||
stmt, err := tx.Prepare(fmt.Sprintf("delete from password_reset_requests where id in (%s)", strings.Join(indexes, ",")))
|
||||
stmt, err := tx.Prepare(fmt.Sprintf("update password_reset_requests set updated_at = current_timestamp, deleted = true where id in (%s)", strings.Join(indexes, ",")))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing password_reset_requests delete multiple statement")
|
||||
}
|
||||
|
@ -16,9 +16,10 @@ type Share struct {
|
||||
FrontendEndpoint *string
|
||||
BackendProxyEndpoint *string
|
||||
Reserved bool
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
func (self *Store) CreateShare(envId int, shr *Share, tx *sqlx.Tx) (int, error) {
|
||||
func (str *Store) CreateShare(envId int, shr *Share, tx *sqlx.Tx) (int, error) {
|
||||
stmt, err := tx.Prepare("insert into shares (environment_id, z_id, token, share_mode, backend_mode, frontend_selection, frontend_endpoint, backend_proxy_endpoint, reserved) values ($1, $2, $3, $4, $5, $6, $7, $8, $9) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing shares insert statement")
|
||||
@ -30,7 +31,7 @@ func (self *Store) CreateShare(envId int, shr *Share, tx *sqlx.Tx) (int, error)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (self *Store) GetShare(id int, tx *sqlx.Tx) (*Share, error) {
|
||||
func (str *Store) GetShare(id int, tx *sqlx.Tx) (*Share, error) {
|
||||
shr := &Share{}
|
||||
if err := tx.QueryRowx("select * from shares where id = $1", id).StructScan(shr); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting share by id")
|
||||
@ -38,8 +39,8 @@ func (self *Store) GetShare(id int, tx *sqlx.Tx) (*Share, error) {
|
||||
return shr, nil
|
||||
}
|
||||
|
||||
func (self *Store) GetAllShares(tx *sqlx.Tx) ([]*Share, error) {
|
||||
rows, err := tx.Queryx("select * from shares order by id")
|
||||
func (str *Store) FindAllShares(tx *sqlx.Tx) ([]*Share, error) {
|
||||
rows, err := tx.Queryx("select * from shares where not deleted order by id")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting all shares")
|
||||
}
|
||||
@ -54,16 +55,24 @@ func (self *Store) GetAllShares(tx *sqlx.Tx) ([]*Share, error) {
|
||||
return shrs, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindShareWithToken(shrToken string, tx *sqlx.Tx) (*Share, error) {
|
||||
func (str *Store) FindShareWithToken(shrToken string, tx *sqlx.Tx) (*Share, error) {
|
||||
shr := &Share{}
|
||||
if err := tx.QueryRowx("select * from shares where token = $1", shrToken).StructScan(shr); err != nil {
|
||||
if err := tx.QueryRowx("select * from shares where token = $1 and not deleted", shrToken).StructScan(shr); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting share by token")
|
||||
}
|
||||
return shr, nil
|
||||
}
|
||||
|
||||
func (self *Store) FindSharesForEnvironment(envId int, tx *sqlx.Tx) ([]*Share, error) {
|
||||
rows, err := tx.Queryx("select shares.* from shares where environment_id = $1", envId)
|
||||
func (str *Store) FindShareWithZIdAndDeleted(zId string, tx *sqlx.Tx) (*Share, error) {
|
||||
shr := &Share{}
|
||||
if err := tx.QueryRowx("select * from shares where z_id = $1", zId).StructScan(shr); err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting share by z_id")
|
||||
}
|
||||
return shr, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindSharesForEnvironment(envId int, tx *sqlx.Tx) ([]*Share, error) {
|
||||
rows, err := tx.Queryx("select shares.* from shares where environment_id = $1 and not deleted", envId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting shares by environment id")
|
||||
}
|
||||
@ -78,7 +87,7 @@ func (self *Store) FindSharesForEnvironment(envId int, tx *sqlx.Tx) ([]*Share, e
|
||||
return shrs, nil
|
||||
}
|
||||
|
||||
func (self *Store) UpdateShare(shr *Share, tx *sqlx.Tx) error {
|
||||
func (str *Store) UpdateShare(shr *Share, tx *sqlx.Tx) error {
|
||||
sql := "update shares set z_id = $1, token = $2, share_mode = $3, backend_mode = $4, frontend_selection = $5, frontend_endpoint = $6, backend_proxy_endpoint = $7, reserved = $8, updated_at = current_timestamp where id = $9"
|
||||
stmt, err := tx.Prepare(sql)
|
||||
if err != nil {
|
||||
@ -91,8 +100,8 @@ func (self *Store) UpdateShare(shr *Share, tx *sqlx.Tx) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *Store) DeleteShare(id int, tx *sqlx.Tx) error {
|
||||
stmt, err := tx.Prepare("delete from shares where id = $1")
|
||||
func (str *Store) DeleteShare(id int, tx *sqlx.Tx) error {
|
||||
stmt, err := tx.Prepare("update shares set updated_at = current_timestamp, deleted = true where id = $1")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing shares delete statement")
|
||||
}
|
||||
|
93
controller/store/shareLimitJournal.go
Normal file
93
controller/store/shareLimitJournal.go
Normal file
@ -0,0 +1,93 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ShareLimitJournal struct {
|
||||
Model
|
||||
ShareId int
|
||||
RxBytes int64
|
||||
TxBytes int64
|
||||
Action LimitJournalAction
|
||||
}
|
||||
|
||||
func (str *Store) CreateShareLimitJournal(j *ShareLimitJournal, trx *sqlx.Tx) (int, error) {
|
||||
stmt, err := trx.Prepare("insert into share_limit_journal (share_id, rx_bytes, tx_bytes, action) values ($1, $2, $3, $4) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing share_limit_journal insert statement")
|
||||
}
|
||||
var id int
|
||||
if err := stmt.QueryRow(j.ShareId, j.RxBytes, j.TxBytes, j.Action).Scan(&id); err != nil {
|
||||
return 0, errors.Wrap(err, "error executing share_limit_journal insert statement")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (str *Store) IsShareLimitJournalEmpty(shrId int, trx *sqlx.Tx) (bool, error) {
|
||||
count := 0
|
||||
if err := trx.QueryRowx("select count(0) from share_limit_journal where share_id = $1", shrId).Scan(&count); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count == 0, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindLatestShareLimitJournal(shrId int, trx *sqlx.Tx) (*ShareLimitJournal, error) {
|
||||
j := &ShareLimitJournal{}
|
||||
if err := trx.QueryRowx("select * from share_limit_journal where share_id = $1 order by created_at desc limit 1", shrId).StructScan(j); err != nil {
|
||||
return nil, errors.Wrap(err, "error finding share_limit_journal by share_id")
|
||||
}
|
||||
return j, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindSelectedLatestShareLimitjournal(shrIds []int, trx *sqlx.Tx) ([]*ShareLimitJournal, error) {
|
||||
if len(shrIds) < 1 {
|
||||
return nil, nil
|
||||
}
|
||||
in := "("
|
||||
for i := range shrIds {
|
||||
if i > 0 {
|
||||
in += ", "
|
||||
}
|
||||
in += fmt.Sprintf("%d", shrIds[i])
|
||||
}
|
||||
in += ")"
|
||||
rows, err := trx.Queryx("select id, share_id, rx_bytes, tx_bytes, action, created_at, updated_at from share_limit_journal where id in (select max(id) as id from share_limit_journal group by share_id) and share_id in " + in)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting all latest share_limit_journal")
|
||||
}
|
||||
var sljs []*ShareLimitJournal
|
||||
for rows.Next() {
|
||||
slj := &ShareLimitJournal{}
|
||||
if err := rows.StructScan(slj); err != nil {
|
||||
return nil, errors.Wrap(err, "error scanning share_limit_journal")
|
||||
}
|
||||
sljs = append(sljs, slj)
|
||||
}
|
||||
return sljs, nil
|
||||
}
|
||||
|
||||
func (str *Store) FindAllLatestShareLimitJournal(trx *sqlx.Tx) ([]*ShareLimitJournal, error) {
|
||||
rows, err := trx.Queryx("select id, share_id, rx_bytes, tx_bytes, action, created_at, updated_at from share_limit_journal where id in (select max(id) as id from share_limit_journal group by share_id)")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error selecting all latest share_limit_journal")
|
||||
}
|
||||
var sljs []*ShareLimitJournal
|
||||
for rows.Next() {
|
||||
slj := &ShareLimitJournal{}
|
||||
if err := rows.StructScan(slj); err != nil {
|
||||
return nil, errors.Wrap(err, "error scanning share_limit_journal")
|
||||
}
|
||||
sljs = append(sljs, slj)
|
||||
}
|
||||
return sljs, nil
|
||||
}
|
||||
|
||||
func (str *Store) DeleteShareLimitJournalForShare(shrId int, trx *sqlx.Tx) error {
|
||||
if _, err := trx.Exec("delete from share_limit_journal where share_id = $1", shrId); err != nil {
|
||||
return errors.Wrapf(err, "error deleting share_limit_journal for '#%d'", shrId)
|
||||
}
|
||||
return nil
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
-- +migrate Up
|
||||
|
||||
alter table account_requests add column deleted boolean not null default(false);
|
||||
alter table accounts add column deleted boolean not null default(false);
|
||||
alter table environments add column deleted boolean not null default(false);
|
||||
alter table frontends add column deleted boolean not null default(false);
|
||||
alter table invite_tokens add column deleted boolean not null default(false);
|
||||
alter table password_reset_requests add column deleted boolean not null default(false);
|
||||
alter table shares add column deleted boolean not null default(false);
|
@ -0,0 +1,33 @@
|
||||
-- +migrate Up
|
||||
|
||||
create type limit_action_type as enum ('clear', 'warning', 'limit');
|
||||
|
||||
create table account_limit_journal (
|
||||
id serial primary key,
|
||||
account_id integer references accounts(id),
|
||||
rx_bytes bigint not null,
|
||||
tx_bytes bigint not null,
|
||||
action limit_action_type not null,
|
||||
created_at timestamptz not null default(current_timestamp),
|
||||
updated_at timestamptz not null default(current_timestamp)
|
||||
);
|
||||
|
||||
create table environment_limit_journal (
|
||||
id serial primary key,
|
||||
environment_id integer references environments(id),
|
||||
rx_bytes bigint not null,
|
||||
tx_bytes bigint not null,
|
||||
action limit_action_type not null,
|
||||
created_at timestamptz not null default(current_timestamp),
|
||||
updated_at timestamptz not null default(current_timestamp)
|
||||
);
|
||||
|
||||
create table share_limit_journal (
|
||||
id serial primary key,
|
||||
share_id integer references shares(id),
|
||||
rx_bytes bigint not null,
|
||||
tx_bytes bigint not null,
|
||||
action limit_action_type not null,
|
||||
created_at timestamptz not null default(current_timestamp),
|
||||
updated_at timestamptz not null default(current_timestamp)
|
||||
);
|
@ -0,0 +1,31 @@
|
||||
-- +migrate Up
|
||||
|
||||
alter table frontends rename to frontends_old;
|
||||
alter sequence frontends_id_seq rename to frontends_id_seq_old;
|
||||
|
||||
create table frontends (
|
||||
id serial primary key,
|
||||
environment_id integer references environments(id),
|
||||
private_share_id integer references shares(id),
|
||||
token varchar(32) not null unique,
|
||||
z_id varchar(32) not null,
|
||||
url_template varchar(1024),
|
||||
public_name varchar(64) unique,
|
||||
reserved boolean not null default(false),
|
||||
created_at timestamptz not null default(current_timestamp),
|
||||
updated_at timestamptz not null default(current_timestamp),
|
||||
deleted boolean not null default(false)
|
||||
);
|
||||
|
||||
insert into frontends (id, environment_id, token, z_id, url_template, public_name, reserved, created_at, updated_at, deleted)
|
||||
select id, environment_id, token, z_id, url_template, public_name, reserved, created_at, updated_at, deleted from frontends_old;
|
||||
|
||||
select setval('frontends_id_seq', (select max(id) from frontends));
|
||||
|
||||
drop table frontends_old;
|
||||
|
||||
alter index frontends_pkey1 rename to frontends_pkey;
|
||||
alter index frontends_public_name_key1 rename to frontends_public_name_key;
|
||||
alter index frontends_token_key1 rename to frontends_token_key;
|
||||
|
||||
alter table frontends rename constraint frontends_environment_id_fkey1 to frontends_environment_id_fkey;
|
@ -0,0 +1,4 @@
|
||||
-- +migrate Up
|
||||
|
||||
alter type backend_mode rename value 'dav' to 'tcpTunnel';
|
||||
alter type backend_mode add value 'udpTunnel';
|
@ -0,0 +1,3 @@
|
||||
-- +migrate Up
|
||||
|
||||
ALTER TABLE account_requests DROP CONSTRAINT account_requests_email_key;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user