1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-27 07:31:46 +01:00

chore: refactors main.go and moves cli args to a new file (#3070)

This commit is contained in:
Amir Raminfar
2024-07-06 10:51:36 -07:00
committed by GitHub
parent 9535b80c10
commit 044c1db55d
6 changed files with 150 additions and 130 deletions

View File

@@ -53,7 +53,7 @@ ARG TARGETOS TARGETARCH
RUN go generate
# Build binary
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags "-s -w -X main.version=$TAG" -o dozzle
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags "-s -w -X cli.version=$TAG" -o dozzle
RUN mkdir /data

View File

@@ -18,4 +18,5 @@ type BeaconEvent struct {
Mode string `json:"mode"`
RemoteAgents int `json:"remoteAgents"`
RemoteClients int `json:"remoteClients"`
SubCommand string `json:"subCommand"`
}

View File

@@ -6,13 +6,14 @@ import (
log "github.com/sirupsen/logrus"
)
func StartEvent(version string, mode string, agents []string, remoteClients []string, client docker.Client) {
func StartEvent(version string, mode string, agents []string, remoteClients []string, client docker.Client, subCommand string) {
event := analytics.BeaconEvent{
Name: "start",
Version: version,
Mode: mode,
RemoteAgents: len(agents),
RemoteClients: len(remoteClients),
SubCommand: subCommand,
}
if client != nil {

View File

@@ -0,0 +1,71 @@
package cli
import (
"strings"
"github.com/alexflint/go-arg"
)
var (
version = "head"
)
type Args struct {
Addr string `arg:"env:DOZZLE_ADDR" default:":8080" help:"sets host:port to bind for server. This is rarely needed inside a docker container."`
Base string `arg:"env:DOZZLE_BASE" default:"/" help:"sets the base for http router."`
Hostname string `arg:"env:DOZZLE_HOSTNAME" help:"sets the hostname for display. This is useful with multiple Dozzle instances."`
Level string `arg:"env:DOZZLE_LEVEL" default:"info" help:"set Dozzle log level. Use debug for more logging."`
AuthProvider string `arg:"--auth-provider,env:DOZZLE_AUTH_PROVIDER" default:"none" help:"sets the auth provider to use. Currently only forward-proxy is supported."`
AuthHeaderUser string `arg:"--auth-header-user,env:DOZZLE_AUTH_HEADER_USER" default:"Remote-User" help:"sets the HTTP Header to use for username in Forward Proxy configuration."`
AuthHeaderEmail string `arg:"--auth-header-email,env:DOZZLE_AUTH_HEADER_EMAIL" default:"Remote-Email" help:"sets the HTTP Header to use for email in Forward Proxy configuration."`
AuthHeaderName string `arg:"--auth-header-name,env:DOZZLE_AUTH_HEADER_NAME" default:"Remote-Name" help:"sets the HTTP Header to use for name in Forward Proxy configuration."`
EnableActions bool `arg:"--enable-actions,env:DOZZLE_ENABLE_ACTIONS" default:"false" help:"enables essential actions on containers from the web interface."`
FilterStrings []string `arg:"env:DOZZLE_FILTER,--filter,separate" help:"filters docker containers using Docker syntax."`
Filter map[string][]string `arg:"-"`
RemoteHost []string `arg:"env:DOZZLE_REMOTE_HOST,--remote-host,separate" help:"list of hosts to connect remotely"`
RemoteAgent []string `arg:"env:DOZZLE_REMOTE_AGENT,--remote-agent,separate" help:"list of agents to connect remotely"`
NoAnalytics bool `arg:"--no-analytics,env:DOZZLE_NO_ANALYTICS" help:"disables anonymous analytics"`
Mode string `arg:"env:DOZZLE_MODE" default:"server" help:"sets the mode to run in (server, swarm)"`
Healthcheck *HealthcheckCmd `arg:"subcommand:healthcheck" help:"checks if the server is running"`
Generate *GenerateCmd `arg:"subcommand:generate" help:"generates a configuration file for simple auth"`
Agent *AgentCmd `arg:"subcommand:agent" help:"starts the agent"`
}
type HealthcheckCmd struct {
}
type AgentCmd struct {
Addr string `arg:"env:DOZZLE_AGENT_ADDR" default:":7007" help:"sets the host:port to bind for the agent"`
}
type GenerateCmd struct {
Username string `arg:"positional"`
Password string `arg:"--password, -p" help:"sets the password for the user"`
Name string `arg:"--name, -n" help:"sets the display name for the user"`
Email string `arg:"--email, -e" help:"sets the email for the user"`
}
func (Args) Version() string {
return version
}
func ParseArgs() (Args, interface{}) {
var args Args
parser := arg.MustParse(&args)
ConfigureLogger(args.Level)
args.Filter = make(map[string][]string)
for _, filter := range args.FilterStrings {
pos := strings.Index(filter, "=")
if pos == -1 {
parser.Fail("each filter should be of the form key=value")
}
key := filter[:pos]
val := filter[pos+1:]
args.Filter[key] = append(args.Filter[key], val)
}
return args, parser.Subcommand()
}

View File

@@ -0,0 +1,63 @@
package cli
import (
"embed"
"github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/docker"
docker_support "github.com/amir20/dozzle/internal/support/docker"
log "github.com/sirupsen/logrus"
)
func CreateMultiHostService(embededCerts embed.FS, args Args) *docker_support.MultiHostService {
var clients []docker_support.ClientService
for _, remoteHost := range args.RemoteHost {
host, err := docker.ParseConnection(remoteHost)
if err != nil {
log.Fatalf("Could not parse remote host %s: %s", remoteHost, err)
}
log.Debugf("creating remote client for %s with %+v", host.Name, host)
log.Infof("Creating client for %s with %s", host.Name, host.URL.String())
if client, err := docker.NewRemoteClient(args.Filter, host); err == nil {
if _, err := client.ListContainers(); err == nil {
log.Debugf("connected to local Docker Engine")
clients = append(clients, docker_support.NewDockerClientService(client))
} else {
log.Warnf("Could not connect to remote host %s: %s", host.ID, err)
}
} else {
log.Warnf("Could not create client for %s: %s", host.ID, err)
}
}
certs, err := ReadCertificates(embededCerts)
if err != nil {
log.Fatalf("Could not read certificates: %v", err)
}
for _, remoteAgent := range args.RemoteAgent {
client, err := agent.NewClient(remoteAgent, certs)
if err != nil {
log.Warnf("Could not connect to remote agent %s: %s", remoteAgent, err)
continue
}
clients = append(clients, docker_support.NewAgentService(client))
}
localClient, err := docker.NewLocalClient(args.Filter, args.Hostname)
if err == nil {
_, err := localClient.ListContainers()
if err != nil {
log.Debugf("could not connect to local Docker Engine: %s", err)
if !args.NoAnalytics {
go StartEvent(version, args.Mode, args.RemoteAgent, args.RemoteHost, nil, "")
}
} else {
log.Debugf("connected to local Docker Engine")
if !args.NoAnalytics {
go StartEvent(version, args.Mode, args.RemoteAgent, args.RemoteHost, localClient, "")
}
clients = append(clients, docker_support.NewDockerClientService(localClient))
}
}
return docker_support.NewMultiHostService(clients)
}

140
main.go
View File

@@ -11,11 +11,9 @@ import (
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/alexflint/go-arg"
"github.com/amir20/dozzle/internal/agent"
"github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/docker"
@@ -27,49 +25,6 @@ import (
log "github.com/sirupsen/logrus"
)
var (
version = "head"
)
type args struct {
Addr string `arg:"env:DOZZLE_ADDR" default:":8080" help:"sets host:port to bind for server. This is rarely needed inside a docker container."`
Base string `arg:"env:DOZZLE_BASE" default:"/" help:"sets the base for http router."`
Hostname string `arg:"env:DOZZLE_HOSTNAME" help:"sets the hostname for display. This is useful with multiple Dozzle instances."`
Level string `arg:"env:DOZZLE_LEVEL" default:"info" help:"set Dozzle log level. Use debug for more logging."`
AuthProvider string `arg:"--auth-provider,env:DOZZLE_AUTH_PROVIDER" default:"none" help:"sets the auth provider to use. Currently only forward-proxy is supported."`
AuthHeaderUser string `arg:"--auth-header-user,env:DOZZLE_AUTH_HEADER_USER" default:"Remote-User" help:"sets the HTTP Header to use for username in Forward Proxy configuration."`
AuthHeaderEmail string `arg:"--auth-header-email,env:DOZZLE_AUTH_HEADER_EMAIL" default:"Remote-Email" help:"sets the HTTP Header to use for email in Forward Proxy configuration."`
AuthHeaderName string `arg:"--auth-header-name,env:DOZZLE_AUTH_HEADER_NAME" default:"Remote-Name" help:"sets the HTTP Header to use for name in Forward Proxy configuration."`
EnableActions bool `arg:"--enable-actions,env:DOZZLE_ENABLE_ACTIONS" default:"false" help:"enables essential actions on containers from the web interface."`
FilterStrings []string `arg:"env:DOZZLE_FILTER,--filter,separate" help:"filters docker containers using Docker syntax."`
Filter map[string][]string `arg:"-"`
RemoteHost []string `arg:"env:DOZZLE_REMOTE_HOST,--remote-host,separate" help:"list of hosts to connect remotely"`
RemoteAgent []string `arg:"env:DOZZLE_REMOTE_AGENT,--remote-agent,separate" help:"list of agents to connect remotely"`
NoAnalytics bool `arg:"--no-analytics,env:DOZZLE_NO_ANALYTICS" help:"disables anonymous analytics"`
Mode string `arg:"env:DOZZLE_MODE" default:"server" help:"sets the mode to run in (server, swarm)"`
Healthcheck *HealthcheckCmd `arg:"subcommand:healthcheck" help:"checks if the server is running"`
Generate *GenerateCmd `arg:"subcommand:generate" help:"generates a configuration file for simple auth"`
Agent *AgentCmd `arg:"subcommand:agent" help:"starts the agent"`
}
type HealthcheckCmd struct {
}
type AgentCmd struct {
Addr string `arg:"env:DOZZLE_AGENT_ADDR" default:":7007" help:"sets the host:port to bind for the agent"`
}
type GenerateCmd struct {
Username string `arg:"positional"`
Password string `arg:"--password, -p" help:"sets the password for the user"`
Name string `arg:"--name, -n" help:"sets the display name for the user"`
Email string `arg:"--email, -e" help:"sets the email for the user"`
}
func (args) Version() string {
return version
}
//go:embed all:dist
var content embed.FS
@@ -78,11 +33,11 @@ var certs embed.FS
//go:generate protoc --go_out=. --go-grpc_out=. --proto_path=./protos ./protos/rpc.proto ./protos/types.proto
func main() {
cli.ValidateEnvVars(args{}, AgentCmd{})
args, subcommand := parseArgs()
cli.ValidateEnvVars(cli.Args{}, cli.AgentCmd{})
args, subcommand := cli.ParseArgs()
if subcommand != nil {
switch subcommand.(type) {
case *AgentCmd:
case *cli.AgentCmd:
client, err := docker.NewLocalClient(args.Filter, args.Hostname)
if err != nil {
log.Fatalf("Could not create docker client: %v", err)
@@ -102,8 +57,10 @@ func main() {
}
defer os.Remove(tempFile.Name())
io.WriteString(tempFile, listener.Addr().String())
go cli.StartEvent(args.Version(), "", args.RemoteAgent, args.RemoteHost, client, "agent")
agent.RunServer(client, certs, listener)
case *HealthcheckCmd:
case *cli.HealthcheckCmd:
go cli.StartEvent(args.Version(), "", args.RemoteAgent, args.RemoteHost, nil, "healthcheck")
files, err := os.ReadDir(".")
if err != nil {
log.Fatalf("Failed to read directory: %v", err)
@@ -134,7 +91,8 @@ func main() {
}
}
case *GenerateCmd:
case *cli.GenerateCmd:
go cli.StartEvent(args.Version(), "", args.RemoteAgent, args.RemoteHost, nil, "generate")
if args.Generate.Username == "" || args.Generate.Password == "" {
log.Fatal("Username and password are required")
}
@@ -158,11 +116,11 @@ func main() {
log.Fatalf("Invalid auth provider %s", args.AuthProvider)
}
log.Infof("Dozzle version %s", version)
log.Infof("Dozzle version %s", args.Version())
var multiHostService *docker_support.MultiHostService
if args.Mode == "server" {
multiHostService = createMultiHostService(args)
multiHostService = cli.CreateMultiHostService(certs, args)
if multiHostService.TotalClients() == 0 {
log.Fatal("Could not connect to any Docker Engines")
} else {
@@ -209,60 +167,7 @@ func main() {
log.Debug("shutdown complete")
}
func createMultiHostService(args args) *docker_support.MultiHostService {
var clients []docker_support.ClientService
for _, remoteHost := range args.RemoteHost {
host, err := docker.ParseConnection(remoteHost)
if err != nil {
log.Fatalf("Could not parse remote host %s: %s", remoteHost, err)
}
log.Debugf("creating remote client for %s with %+v", host.Name, host)
log.Infof("Creating client for %s with %s", host.Name, host.URL.String())
if client, err := docker.NewRemoteClient(args.Filter, host); err == nil {
if _, err := client.ListContainers(); err == nil {
log.Debugf("connected to local Docker Engine")
clients = append(clients, docker_support.NewDockerClientService(client))
} else {
log.Warnf("Could not connect to remote host %s: %s", host.ID, err)
}
} else {
log.Warnf("Could not create client for %s: %s", host.ID, err)
}
}
certs, err := cli.ReadCertificates(certs)
if err != nil {
log.Fatalf("Could not read certificates: %v", err)
}
for _, remoteAgent := range args.RemoteAgent {
client, err := agent.NewClient(remoteAgent, certs)
if err != nil {
log.Warnf("Could not connect to remote agent %s: %s", remoteAgent, err)
continue
}
clients = append(clients, docker_support.NewAgentService(client))
}
localClient, err := docker.NewLocalClient(args.Filter, args.Hostname)
if err == nil {
_, err := localClient.ListContainers()
if err != nil {
log.Debugf("could not connect to local Docker Engine: %s", err)
if !args.NoAnalytics {
go cli.StartEvent(version, args.Mode, args.RemoteAgent, args.RemoteHost, nil)
}
} else {
log.Debugf("connected to local Docker Engine")
if !args.NoAnalytics {
go cli.StartEvent(version, args.Mode, args.RemoteAgent, args.RemoteHost, localClient)
}
clients = append(clients, docker_support.NewDockerClientService(localClient))
}
}
return docker_support.NewMultiHostService(clients)
}
func createServer(args args, multiHostService *docker_support.MultiHostService) *http.Server {
func createServer(args cli.Args, multiHostService *docker_support.MultiHostService) *http.Server {
_, dev := os.LookupEnv("DEV")
var provider web.AuthProvider = web.NONE
@@ -291,7 +196,7 @@ func createServer(args args, multiHostService *docker_support.MultiHostService)
config := web.Config{
Addr: args.Addr,
Base: args.Base,
Version: version,
Version: args.Version(),
Hostname: args.Hostname,
NoAnalytics: args.NoAnalytics,
Dev: dev,
@@ -328,24 +233,3 @@ func createServer(args args, multiHostService *docker_support.MultiHostService)
return web.CreateServer(multiHostService, assets, config)
}
func parseArgs() (args, interface{}) {
var args args
parser := arg.MustParse(&args)
cli.ConfigureLogger(args.Level)
args.Filter = make(map[string][]string)
for _, filter := range args.FilterStrings {
pos := strings.Index(filter, "=")
if pos == -1 {
parser.Fail("each filter should be of the form key=value")
}
key := filter[:pos]
val := filter[pos+1:]
args.Filter[key] = append(args.Filter[key], val)
}
return args, parser.Subcommand()
}