Client mode
* Refactored so that all server code is in a single file * Added missing CLI option to send reload/quit commands to the server * Implemented client mode
This commit is contained in:
parent
cd295e51ba
commit
be6198dbed
3 changed files with 196 additions and 92 deletions
84
client.go
Normal file
84
client.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Client runtime data
|
||||
type TClient struct {
|
||||
config tConfiguration
|
||||
}
|
||||
|
||||
// Initialize the client's state.
|
||||
func InitClient(config tConfiguration) TClient {
|
||||
return TClient{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to the UNIX socket. Terminate the program with an error if connection
|
||||
// fails.
|
||||
func (c *TClient) getConnection() net.Conn {
|
||||
conn, err := net.Dial("unix", c.config.Socket.Path)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"path": c.config.Socket.Path,
|
||||
}).Fatal("Could not connect to the UNIX socket")
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
// Send a string to the UNIX socket. Terminate the program with an error if
|
||||
// some form of IO error occurs.
|
||||
func (c *TClient) send(conn net.Conn, data string) {
|
||||
_, err := conn.Write([]byte(data))
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"path": c.config.Socket.Path,
|
||||
"data": data,
|
||||
}).Fatal("Could not write to the UNIX socket")
|
||||
}
|
||||
}
|
||||
|
||||
// Send a command to the server then disconnect.
|
||||
func (c *TClient) SendCommand(command string) {
|
||||
conn := c.getConnection()
|
||||
defer conn.Close()
|
||||
c.send(conn, command)
|
||||
}
|
||||
|
||||
// Request an update by sending the selector and force flag to the server, then
|
||||
// wait for the server to respond. Returns true if the server responded that the
|
||||
// updates were executed without problem.
|
||||
func (c *TClient) RequestUpdate(selector string, force bool) bool {
|
||||
command := "U"
|
||||
if force {
|
||||
command += "!"
|
||||
} else {
|
||||
command += " "
|
||||
}
|
||||
command += selector
|
||||
|
||||
conn := c.getConnection()
|
||||
defer conn.Close()
|
||||
c.send(conn, command)
|
||||
|
||||
buf := make([]byte, 2)
|
||||
nr, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
log.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"path": c.config.Socket.Path,
|
||||
}).Fatal("Could not read server response from the UNIX socket")
|
||||
}
|
||||
if nr != 1 {
|
||||
log.WithFields(logrus.Fields{
|
||||
"path": c.config.Socket.Path,
|
||||
}).Fatal("Invalid response from server")
|
||||
}
|
||||
return buf[0] == 49
|
||||
}
|
111
main.go
111
main.go
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/karrick/golf"
|
||||
|
@ -16,6 +15,11 @@ type (
|
|||
// then quits), client (connects to the server and requests an
|
||||
// update) or server (runs the server in the foreground).
|
||||
runMode string
|
||||
// When running in client mode, if this is set to a string, it
|
||||
// will be interpreted as a command to send to the server.
|
||||
// Supported commands are 'Q' (quit) and 'R' (reload
|
||||
// configuration)
|
||||
command string
|
||||
// The selector to use when running the updates. Only meaningful
|
||||
// if running in client or standalone mode.
|
||||
selector string
|
||||
|
@ -33,16 +37,6 @@ type (
|
|||
// Send logs to syslog.
|
||||
logSyslog bool
|
||||
}
|
||||
|
||||
// The state of the main server
|
||||
tServerState struct {
|
||||
// The path to the configuration file
|
||||
cfgFile string
|
||||
// The configuration
|
||||
config tConfiguration
|
||||
// The UNIX socket listener
|
||||
listener net.Listener
|
||||
}
|
||||
)
|
||||
|
||||
// Parse command line options.
|
||||
|
@ -52,6 +46,10 @@ func parseCommandLine() tCliFlags {
|
|||
|
||||
golf.StringVarP(&flags.cfgFile, 'c', "config", "/etc/fetch-certificates.yml",
|
||||
"Path to the configuration file.")
|
||||
golf.StringVarP(&flags.command, 'C', "command", "",
|
||||
"Send a command to the server instead of requesting an "+
|
||||
"update. Only meaningful in client mode. Command may be "+
|
||||
"Q (quit) or R (reload configuration).")
|
||||
golf.BoolVarP(&flags.force, 'f', "force", false,
|
||||
"Force update of selected certificates. Only meaningful in "+
|
||||
"client or standalone mode.")
|
||||
|
@ -81,65 +79,6 @@ func parseCommandLine() tCliFlags {
|
|||
return flags
|
||||
}
|
||||
|
||||
// Initialize server state
|
||||
func initServer(cfgFile string) tServerState {
|
||||
ss := tServerState{
|
||||
cfgFile: cfgFile,
|
||||
}
|
||||
cfg, err := LoadConfiguration(ss.cfgFile)
|
||||
if err != nil {
|
||||
log.WithField("error", err).Fatal("Failed to load initial configuration.")
|
||||
}
|
||||
ss.config = cfg
|
||||
listener, err := initSocket(cfg.Socket)
|
||||
if err != nil {
|
||||
log.WithField("error", err).Fatal("Failed to initialize socket.")
|
||||
}
|
||||
ss.listener = listener
|
||||
return ss
|
||||
}
|
||||
|
||||
// Destroy the server
|
||||
func (state *tServerState) destroy() {
|
||||
state.listener.Close()
|
||||
}
|
||||
|
||||
// Server main loop. Processes commands received from connections. Certificate
|
||||
// update requests are processed directly, but Quit/Reload commands are
|
||||
// propagated back to this loop and handled here.
|
||||
func (state *tServerState) mainLoop() {
|
||||
for {
|
||||
cmd := socketServer(&state.config, state.listener)
|
||||
if cmd == CMD_QUIT {
|
||||
break
|
||||
} else if cmd != CMD_RELOAD {
|
||||
continue
|
||||
}
|
||||
|
||||
new_cfg, err := LoadConfiguration(state.cfgFile)
|
||||
if err != nil {
|
||||
log.WithField("error", err).Error("Failed to load updated configuration.")
|
||||
continue
|
||||
}
|
||||
|
||||
replace_ok := true
|
||||
if new_cfg.Socket.Path != state.config.Socket.Path {
|
||||
new_listener, err := initSocket(new_cfg.Socket)
|
||||
if err != nil {
|
||||
log.WithField("error", err).Error("Failed to initialize new server socket.")
|
||||
replace_ok = false
|
||||
} else {
|
||||
state.listener.Close()
|
||||
state.listener = new_listener
|
||||
}
|
||||
}
|
||||
if replace_ok {
|
||||
state.config = new_cfg
|
||||
log.Info("Configuration reloaded")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flags := parseCommandLine()
|
||||
err := configureLogging(flags)
|
||||
|
@ -147,26 +86,40 @@ func main() {
|
|||
log.WithField("error", err).Fatal("Failed to configure logging.")
|
||||
}
|
||||
|
||||
if flags.runMode == "server" {
|
||||
server := initServer(flags.cfgFile)
|
||||
defer server.destroy()
|
||||
server.mainLoop()
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := LoadConfiguration(flags.cfgFile)
|
||||
if err != nil {
|
||||
log.WithField("error", err).Fatal("Failed to load initial configuration.")
|
||||
}
|
||||
if flags.runMode == "standalone" {
|
||||
|
||||
if flags.runMode == "server" {
|
||||
server := InitServer(flags.cfgFile, cfg)
|
||||
defer server.Destroy()
|
||||
server.MainLoop()
|
||||
|
||||
} else if flags.runMode == "standalone" {
|
||||
result := executeUpdate(&cfg, flags.selector, flags.force)
|
||||
if result {
|
||||
log.Debug("Update successful")
|
||||
} else {
|
||||
log.Fatal("Update failed")
|
||||
}
|
||||
|
||||
} else if flags.runMode == "client" {
|
||||
panic("CLIENT MODE NOT IMPLEMENTED") // FIXME
|
||||
client := InitClient(cfg)
|
||||
if flags.command == "Q" || flags.command == "R" {
|
||||
client.SendCommand(flags.command)
|
||||
} else if flags.command != "" {
|
||||
log.WithField("command", flags.command).Fatal(
|
||||
"Unknown server command.")
|
||||
} else {
|
||||
result := client.RequestUpdate(flags.selector, flags.force)
|
||||
if result {
|
||||
log.Debug("Update successful")
|
||||
} else {
|
||||
log.Fatal("Update failed")
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log.WithField("mode", flags.runMode).Fatal("Unknown execution mode.")
|
||||
}
|
||||
|
|
|
@ -12,21 +12,33 @@ import (
|
|||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type TCommandType int
|
||||
type tCommandType int
|
||||
|
||||
const (
|
||||
CMD_IGNORE TCommandType = iota
|
||||
CMD_IGNORE tCommandType = iota
|
||||
CMD_QUIT
|
||||
CMD_RELOAD
|
||||
CMD_UPDATE
|
||||
)
|
||||
|
||||
type TCommand struct {
|
||||
CommandType TCommandType
|
||||
type (
|
||||
tCommand struct {
|
||||
CommandType tCommandType
|
||||
Force bool
|
||||
Selector string
|
||||
}
|
||||
|
||||
// The state of the main server
|
||||
TServerState struct {
|
||||
// The path to the configuration file
|
||||
cfgFile string
|
||||
// The configuration
|
||||
config tConfiguration
|
||||
// The UNIX socket listener
|
||||
listener net.Listener
|
||||
}
|
||||
)
|
||||
|
||||
func configureSocket(cfg tSocketConfig) error {
|
||||
if cfg.Group != "" {
|
||||
group, err := user.LookupGroup(cfg.Group)
|
||||
|
@ -67,7 +79,7 @@ func initSocket(cfg tSocketConfig) (net.Listener, error) {
|
|||
return listener, nil
|
||||
}
|
||||
|
||||
func socketServer(cfg *tConfiguration, listener net.Listener) TCommandType {
|
||||
func socketServer(cfg *tConfiguration, listener net.Listener) tCommandType {
|
||||
for {
|
||||
fd, err := listener.Accept()
|
||||
if err != nil {
|
||||
|
@ -80,7 +92,7 @@ func socketServer(cfg *tConfiguration, listener net.Listener) TCommandType {
|
|||
}
|
||||
}
|
||||
|
||||
func executeFromSocket(cfg *tConfiguration, conn net.Conn) TCommandType {
|
||||
func executeFromSocket(cfg *tConfiguration, conn net.Conn) tCommandType {
|
||||
defer conn.Close()
|
||||
log.Debug("Received connection")
|
||||
|
||||
|
@ -114,7 +126,7 @@ func executeFromSocket(cfg *tConfiguration, conn net.Conn) TCommandType {
|
|||
return command.CommandType
|
||||
}
|
||||
|
||||
func parseCommand(n int, buf []byte) *TCommand {
|
||||
func parseCommand(n int, buf []byte) *tCommand {
|
||||
if n == 512 {
|
||||
log.Warn("Too much data received")
|
||||
return nil
|
||||
|
@ -125,12 +137,12 @@ func parseCommand(n int, buf []byte) *TCommand {
|
|||
}
|
||||
if n == 1 {
|
||||
if buf[0] == 'Q' {
|
||||
return &TCommand{CommandType: CMD_QUIT}
|
||||
return &tCommand{CommandType: CMD_QUIT}
|
||||
} else if buf[0] == 'R' {
|
||||
return &TCommand{CommandType: CMD_RELOAD}
|
||||
return &tCommand{CommandType: CMD_RELOAD}
|
||||
}
|
||||
} else if n > 2 && buf[0] == 'U' {
|
||||
res := &TCommand{CommandType: CMD_UPDATE}
|
||||
res := &tCommand{CommandType: CMD_UPDATE}
|
||||
if buf[1] == '!' {
|
||||
res.Force = true
|
||||
}
|
||||
|
@ -142,3 +154,58 @@ func parseCommand(n int, buf []byte) *TCommand {
|
|||
log.Warn("Invalid command received")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Initialize server state
|
||||
func InitServer(cfgFile string, config tConfiguration) TServerState {
|
||||
ss := TServerState{
|
||||
cfgFile: cfgFile,
|
||||
config: config,
|
||||
}
|
||||
listener, err := initSocket(ss.config.Socket)
|
||||
if err != nil {
|
||||
log.WithField("error", err).Fatal("Failed to initialize socket.")
|
||||
}
|
||||
ss.listener = listener
|
||||
return ss
|
||||
}
|
||||
|
||||
// Destroy the server
|
||||
func (state *TServerState) Destroy() {
|
||||
state.listener.Close()
|
||||
}
|
||||
|
||||
// Server main loop. Processes commands received from connections. Certificate
|
||||
// update requests are processed directly, but Quit/Reload commands are
|
||||
// propagated back to this loop and handled here.
|
||||
func (state *TServerState) MainLoop() {
|
||||
for {
|
||||
cmd := socketServer(&state.config, state.listener)
|
||||
if cmd == CMD_QUIT {
|
||||
break
|
||||
} else if cmd != CMD_RELOAD {
|
||||
continue
|
||||
}
|
||||
|
||||
new_cfg, err := LoadConfiguration(state.cfgFile)
|
||||
if err != nil {
|
||||
log.WithField("error", err).Error("Failed to load updated configuration.")
|
||||
continue
|
||||
}
|
||||
|
||||
replace_ok := true
|
||||
if new_cfg.Socket.Path != state.config.Socket.Path {
|
||||
new_listener, err := initSocket(new_cfg.Socket)
|
||||
if err != nil {
|
||||
log.WithField("error", err).Error("Failed to initialize new server socket.")
|
||||
replace_ok = false
|
||||
} else {
|
||||
state.listener.Close()
|
||||
state.listener = new_listener
|
||||
}
|
||||
}
|
||||
if replace_ok {
|
||||
state.config = new_cfg
|
||||
log.Info("Configuration reloaded")
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue