/
config_env.go
142 lines (120 loc) · 4.2 KB
/
config_env.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package v2
import (
"fmt"
"os"
"strings"
"github.com/cyberark/secretless-broker/pkg/secretless/log"
"github.com/cyberark/secretless-broker/pkg/secretless/plugin"
"github.com/go-ozzo/ozzo-validation"
)
// DeleteFileFunc is a function that takes a filename, attempts to delete the
// file, and returns an error if it can't.
type DeleteFileFunc func(name string) error
// FileInfoFunc is a function that takes a filename and returns information
// about that file, or an error if it cannot be found or read.
type FileInfoFunc func(name string) (os.FileInfo, error)
// ConfigEnv represents the runtime environment that will fulfill the services
// requested by the Config. It has a single public method, Prepare, that
// ensures the runtime environment supports the requested services.
type ConfigEnv struct {
availPlugins plugin.AvailablePlugins
deleteFile DeleteFileFunc
getFileInfo FileInfoFunc
logger log.Logger
}
// NewConfigEnv creates a new instance of ConfigEnv.
func NewConfigEnv(logger log.Logger, availPlugins plugin.AvailablePlugins) ConfigEnv {
return NewConfigEnvWithOptions(logger, availPlugins, os.Stat, os.Remove)
}
// NewConfigEnvWithOptions allows injecting all dependencies. Used for unit
// testing.
func NewConfigEnvWithOptions(
logger log.Logger,
availPlugins plugin.AvailablePlugins,
getFileInfo func(name string) (os.FileInfo, error),
deleteFile func(name string) error,
) ConfigEnv {
return ConfigEnv{
logger: logger,
availPlugins: availPlugins,
getFileInfo: getFileInfo,
deleteFile: deleteFile,
}
}
// Prepare ensures the runtime environment is prepared to handle the Config's
// service requests. It checks both that the requested connectors exist, and
// that the requested sockets are available, or can be deleted. If any of these
// checks fail, it will error.
func (c *ConfigEnv) Prepare(cfg Config) error {
err := c.validateRequestedPlugins(cfg)
if err != nil {
return err
}
return c.ensureAllSocketsAreDeleted(cfg)
}
// validateRequestedPlugins ensures that the AvailablePlugins can fulfill the
// services requested by the given Config, and return an error if not.
func (c *ConfigEnv) validateRequestedPlugins(cfg Config) error {
pluginIDs := plugin.AvailableConnectorIDs(c.availPlugins)
c.logger.Infof(
"Validating config against available plugins: %s",
strings.Join(pluginIDs, ","),
)
// Convert available plugin IDs to a map, so that we can check if they exist
// in the loop below using a map lookup rather than a nested loop.
pluginIDsMap := map[string]bool{}
for _, p := range pluginIDs {
pluginIDsMap[p] = true
}
errors := validation.Errors{}
for _, service := range cfg.Services {
// A plugin ID and a connector name are equivalent.
pluginExists := pluginIDsMap[service.Connector]
if !pluginExists {
errors[service.Name] = fmt.Errorf(
`missing service connector "%s"`,
service.Connector,
)
continue
}
}
err := errors.Filter()
if err != nil {
err = fmt.Errorf("services validation failed: %s", err.Error())
}
return err
}
func (c *ConfigEnv) ensureAllSocketsAreDeleted(cfg Config) error {
errors := validation.Errors{}
for _, service := range cfg.Services {
err := c.ensureSocketIsDeleted(service.ListenOn)
if err != nil {
errors[service.Name] = fmt.Errorf(
"socket can't be deleted: %s", service.ListenOn,
)
}
}
return errors.Filter()
}
func (c *ConfigEnv) ensureSocketIsDeleted(address NetworkAddress) error {
// If we're not a unix socket address, we don't need to worry about
// pre-emptive cleanup
if address.Network() != "unix" {
return nil
}
socketFile := address.Address()
c.logger.Debugf("Ensuring that the socketfile '%s' is not present...", socketFile)
// If file is not present, then we are ok to continue.
// NOTE: os.IsNotExist is a pure function, so does not need to be injected.
if _, err := c.getFileInfo(socketFile); os.IsNotExist(err) {
c.logger.Debugf("Socket file '%s' not present. Skipping deletion.", socketFile)
return nil
}
// Otherwise delete the file first
c.logger.Warnf("Socket file '%s' already present. Deleting...", socketFile)
err := c.deleteFile(socketFile)
if err != nil {
return fmt.Errorf("unable to delete stale socket file '%s'", socketFile)
}
return nil
}