1b95590d |
package main
import ( |
59d45c38 |
"context" |
bfed4b7c |
"crypto/tls" |
007ef161 |
"fmt"
"os"
"path/filepath" |
96ce3a19 |
"strings" |
531f4122 |
"time" |
007ef161 |
|
7841d6ab |
"github.com/docker/distribution/uuid" |
8d346762 |
"github.com/docker/docker/api" |
a0bf80fe |
apiserver "github.com/docker/docker/api/server" |
0296797f |
buildbackend "github.com/docker/docker/api/server/backend/build" |
8d346762 |
"github.com/docker/docker/api/server/middleware" |
a793564b |
"github.com/docker/docker/api/server/router" |
1af76ef5 |
"github.com/docker/docker/api/server/router/build" |
3976a33c |
checkpointrouter "github.com/docker/docker/api/server/router/checkpoint" |
1af76ef5 |
"github.com/docker/docker/api/server/router/container" |
41b27de4 |
distributionrouter "github.com/docker/docker/api/server/router/distribution" |
1af76ef5 |
"github.com/docker/docker/api/server/router/image"
"github.com/docker/docker/api/server/router/network" |
c410222e |
pluginrouter "github.com/docker/docker/api/server/router/plugin" |
ec7b6238 |
sessionrouter "github.com/docker/docker/api/server/router/session" |
534a90a9 |
swarmrouter "github.com/docker/docker/api/server/router/swarm" |
1af76ef5 |
systemrouter "github.com/docker/docker/api/server/router/system"
"github.com/docker/docker/api/server/router/volume" |
5c3d2d55 |
"github.com/docker/docker/builder/dockerfile"
"github.com/docker/docker/builder/fscache" |
ce375503 |
"github.com/docker/docker/cli/debug" |
63503caf |
"github.com/docker/docker/daemon" |
534a90a9 |
"github.com/docker/docker/daemon/cluster" |
db63f937 |
"github.com/docker/docker/daemon/config" |
c204fce2 |
"github.com/docker/docker/daemon/listeners" |
9b782d3a |
"github.com/docker/docker/daemon/logger" |
8054a303 |
"github.com/docker/docker/dockerversion" |
9c4570a9 |
"github.com/docker/docker/libcontainerd" |
fb833947 |
dopts "github.com/docker/docker/opts" |
8d346762 |
"github.com/docker/docker/pkg/authorization" |
27cfa68a |
"github.com/docker/docker/pkg/jsonmessage" |
531f4122 |
"github.com/docker/docker/pkg/pidfile" |
2b045027 |
"github.com/docker/docker/pkg/plugingetter" |
c9f3fd3f |
"github.com/docker/docker/pkg/signal" |
fe5b34ba |
"github.com/docker/docker/pkg/system" |
38de272b |
"github.com/docker/docker/plugin" |
afade423 |
"github.com/docker/docker/registry" |
f0d26e16 |
"github.com/docker/docker/runconfig" |
8e034802 |
"github.com/docker/go-connections/tlsconfig" |
59d45c38 |
swarmapi "github.com/docker/swarmkit/api" |
41445a47 |
"github.com/moby/buildkit/session" |
ec7b6238 |
"github.com/pkg/errors" |
1009e6a4 |
"github.com/sirupsen/logrus" |
fb833947 |
"github.com/spf13/pflag" |
1b95590d |
)
|
677a6b35 |
// DaemonCli represents the daemon CLI.
type DaemonCli struct { |
db63f937 |
*config.Config |
fb833947 |
configFile *string
flags *pflag.FlagSet |
57aef3b4 |
|
4192fe9c |
api *apiserver.Server
d *daemon.Daemon
authzMiddleware *authorization.Middleware // authzMiddleware enables to dynamically reload the authorization plugins |
677a6b35 |
}
|
fb833947 |
// NewDaemonCli returns a daemon CLI |
96ce3a19 |
func NewDaemonCli() *DaemonCli { |
fb833947 |
return &DaemonCli{} |
353b7c8e |
}
|
9ff9a91a |
func (cli *DaemonCli) start(opts *daemonOptions) (err error) { |
57aef3b4 |
stopc := make(chan bool)
defer close(stopc)
|
7841d6ab |
// warn from uuid package when running the daemon
uuid.Loggerf = logrus.Warnf
|
9ff9a91a |
opts.SetDefaultOptions(opts.flags) |
96ce3a19 |
|
fb833947 |
if cli.Config, err = loadDaemonCliConfig(opts); err != nil { |
57aef3b4 |
return err |
677a6b35 |
} |
fb833947 |
cli.configFile = &opts.configFile
cli.flags = opts.flags |
677a6b35 |
if cli.Config.Debug { |
ce375503 |
debug.Enable() |
677a6b35 |
} |
78578125 |
|
7781a1bf |
if cli.Config.Experimental { |
96ce3a19 |
logrus.Warn("Running experimental build") |
1b95590d |
} |
711e5803 |
|
87a450a3 |
logrus.SetFormatter(&logrus.TextFormatter{ |
27cfa68a |
TimestampFormat: jsonmessage.RFC3339NanoFixed, |
87a450a3 |
DisableColors: cli.Config.RawLogs, |
af64e396 |
FullTimestamp: true, |
87a450a3 |
}) |
711e5803 |
|
ff686743 |
system.InitLCOW(cli.Config.Experimental)
|
6578ad90 |
if err := setDefaultUmask(); err != nil { |
57aef3b4 |
return fmt.Errorf("Failed to set umask: %v", err) |
6578ad90 |
}
|
96ce3a19 |
if len(cli.LogConfig.Config) > 0 {
if err := logger.ValidateLogOpts(cli.LogConfig.Type, cli.LogConfig.Config); err != nil { |
57aef3b4 |
return fmt.Errorf("Failed to set log opts: %v", err) |
9b782d3a |
}
}
|
46ec4c1a |
// Create the daemon root before we create ANY other files (PID, or migrate keys)
// to ensure the appropriate ACL is set (particularly relevant on Windows)
if err := daemon.CreateDaemonRoot(cli.Config); err != nil {
return err
}
|
96ce3a19 |
if cli.Pidfile != "" {
pf, err := pidfile.New(cli.Pidfile) |
531f4122 |
if err != nil { |
57aef3b4 |
return fmt.Errorf("Error starting daemon: %v", err) |
531f4122 |
}
defer func() { |
57aef3b4 |
if err := pf.Remove(); err != nil { |
531f4122 |
logrus.Error(err)
}
}()
} |
afade423 |
|
8f68adfa |
// TODO: extract to newApiServerConfig() |
351f6b8e |
serverConfig := &apiserver.Config{ |
8d346762 |
Logging: true,
SocketGroup: cli.Config.SocketGroup,
Version: dockerversion.Version, |
2feb88cb |
CorsHeaders: cli.Config.CorsHeaders, |
bfed4b7c |
}
|
677a6b35 |
if cli.Config.TLS {
tlsOptions := tlsconfig.Options{ |
ddd5278b |
CAFile: cli.Config.CommonTLSOptions.CAFile,
CertFile: cli.Config.CommonTLSOptions.CertFile,
KeyFile: cli.Config.CommonTLSOptions.KeyFile,
ExclusiveRootPools: true, |
677a6b35 |
}
if cli.Config.TLSVerify { |
96ce3a19 |
// server requires and verifies client's certificate |
677a6b35 |
tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert |
bfed4b7c |
} |
677a6b35 |
tlsConfig, err := tlsconfig.Server(tlsOptions) |
bfed4b7c |
if err != nil { |
57aef3b4 |
return err |
bfed4b7c |
}
serverConfig.TLSConfig = tlsConfig |
50f09060 |
}
|
677a6b35 |
if len(cli.Config.Hosts) == 0 {
cli.Config.Hosts = make([]string, 1) |
e38767e1 |
} |
34c29277 |
|
8f68adfa |
cli.api = apiserver.New(serverConfig) |
34c29277 |
|
7318eba5 |
var hosts []string
|
677a6b35 |
for i := 0; i < len(cli.Config.Hosts); i++ { |
50f09060 |
var err error |
fb833947 |
if cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil { |
57aef3b4 |
return fmt.Errorf("error parsing -H %s : %v", cli.Config.Hosts[i], err) |
50f09060 |
} |
677a6b35 |
protoAddr := cli.Config.Hosts[i] |
5eda566f |
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
if len(protoAddrParts) != 2 { |
57aef3b4 |
return fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr) |
5eda566f |
} |
5ee0a941 |
proto := protoAddrParts[0]
addr := protoAddrParts[1]
// It's a bad idea to bind to TCP without tlsverify.
if proto == "tcp" && (serverConfig.TLSConfig == nil || serverConfig.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert) { |
f25e5cee |
logrus.Warn("[!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting --tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]") |
5ee0a941 |
} |
3d6f5984 |
ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig) |
34c29277 |
if err != nil { |
57aef3b4 |
return err |
34c29277 |
} |
3d6f5984 |
ls = wrapListeners(proto, ls) |
5ee0a941 |
// If we're binding to a TCP port, make sure that a container doesn't try to use it.
if proto == "tcp" {
if err := allocateDaemonPort(addr); err != nil { |
57aef3b4 |
return err |
5ee0a941 |
}
} |
dc4dcf89 |
logrus.Debugf("Listener created for HTTP on %s (%s)", proto, addr) |
7318eba5 |
hosts = append(hosts, protoAddrParts[1]) |
8f68adfa |
cli.api.Accept(addr, ls...) |
5eda566f |
} |
a0bf80fe |
|
5258297d |
registryService, err := registry.NewService(cli.Config.ServiceOptions)
if err != nil {
return err
}
|
77a50ffa |
containerdRemote, err := libcontainerd.New(cli.getLibcontainerdRoot(), cli.getPlatformRemoteOptions()...) |
9c4570a9 |
if err != nil { |
57aef3b4 |
return err |
9c4570a9 |
} |
3c25656e |
signal.Trap(func() {
cli.stop()
<-stopc // wait for daemonCli.start() to return |
cc4da811 |
}, logrus.StandardLogger()) |
9c4570a9 |
|
e128a656 |
// Notify that the API is active, but before daemon is set up.
preNotifySystem()
|
38de272b |
pluginStore := plugin.NewStore()
|
8f68adfa |
if err := cli.initMiddlewares(cli.api, serverConfig, pluginStore); err != nil { |
38de272b |
logrus.Fatalf("Error creating middlewares: %v", err)
}
d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore) |
08230703 |
if err != nil { |
57aef3b4 |
return fmt.Errorf("Error starting daemon: %v", err) |
08230703 |
}
|
7318eba5 |
d.StoreHosts(hosts)
|
38de272b |
// validate after NewDaemon has restored enabled plugins. Dont change order.
if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, pluginStore); err != nil {
return fmt.Errorf("Error validating authorization plugin: %v", err)
}
|
8f68adfa |
// TODO: move into startMetricsServer() |
3343d234 |
if cli.Config.MetricsAddress != "" {
if !d.HasExperimental() {
return fmt.Errorf("metrics-addr is only supported when experimental is enabled")
}
if err := startMetricsServer(cli.Config.MetricsAddress); err != nil {
return err
}
}
|
8f68adfa |
// TODO: createAndStartCluster() |
534a90a9 |
name, _ := os.Hostname()
|
59d45c38 |
// Use a buffered channel to pass changes from store watch API to daemon
// A buffer allows store watch API and daemon processing to not wait for each other
watchStream := make(chan *swarmapi.WatchMessage, 32)
|
534a90a9 |
c, err := cluster.New(cluster.Config{ |
a0ccd0d4 |
Root: cli.Config.Root,
Name: name,
Backend: d, |
72c3bcf2 |
PluginBackend: d.PluginManager(), |
a0ccd0d4 |
NetworkSubnetsProvider: d,
DefaultAdvertiseAddr: cli.Config.SwarmDefaultAdvertiseAddr, |
4d95ea31 |
RuntimeRoot: cli.getSwarmRunRoot(), |
59d45c38 |
WatchStream: watchStream, |
534a90a9 |
})
if err != nil {
logrus.Fatalf("Error creating cluster component: %v", err)
} |
e2ec0067 |
d.SetCluster(c)
err = c.Start()
if err != nil {
logrus.Fatalf("Error starting cluster component: %v", err)
} |
534a90a9 |
|
c9fb551d |
// Restart all autostart containers which has a swarm endpoint
// and is not yet running now that we have successfully
// initialized the cluster.
d.RestartSwarmContainers()
|
08230703 |
logrus.Info("Daemon has completed initialization")
|
c5393ee1 |
cli.d = d
|
8f68adfa |
routerOptions, err := newRouterOptions(cli.Config, d)
if err != nil {
return err
}
routerOptions.api = cli.api
routerOptions.cluster = c
initRouter(routerOptions) |
da982cf5 |
|
59d45c38 |
// process cluster change notifications
watchCtx, cancel := context.WithCancel(context.Background())
defer cancel()
go d.ProcessClusterNotifications(watchCtx, watchStream)
|
57aef3b4 |
cli.setupConfigReloadTrap() |
677a6b35 |
|
a8b84cd8 |
// The serve API routine never exits unless an error occurs
// We need to start it as a goroutine and wait on it so
// daemon doesn't exit
serveAPIWait := make(chan error) |
8f68adfa |
go cli.api.Wait(serveAPIWait) |
a8b84cd8 |
|
ca5795ce |
// after the daemon is done setting up we can notify systemd api |
da982cf5 |
notifySystem() |
181fea24 |
|
459e58ff |
// Daemon is fully initialized and handling API traffic |
531f4122 |
// Wait for serve API to complete |
459e58ff |
errAPI := <-serveAPIWait |
534a90a9 |
c.Cleanup() |
cc703784 |
shutdownDaemon(d) |
9c4570a9 |
containerdRemote.Cleanup() |
459e58ff |
if errAPI != nil { |
57aef3b4 |
return fmt.Errorf("Shutting down due to ServeAPI error: %v", errAPI)
}
return nil
}
|
8f68adfa |
type routerOptions struct {
sessionManager *session.Manager
buildBackend *buildbackend.Backend
buildCache *fscache.FSCache
daemon *daemon.Daemon
api *apiserver.Server
cluster *cluster.Cluster
}
func newRouterOptions(config *config.Config, daemon *daemon.Daemon) (routerOptions, error) {
opts := routerOptions{}
sm, err := session.NewManager()
if err != nil {
return opts, errors.Wrap(err, "failed to create sessionmanager")
}
builderStateDir := filepath.Join(config.Root, "builder")
buildCache, err := fscache.NewFSCache(fscache.Opt{
Backend: fscache.NewNaiveCacheBackend(builderStateDir),
Root: builderStateDir,
GCPolicy: fscache.GCPolicy{ // TODO: expose this in config
MaxSize: 1024 * 1024 * 512, // 512MB
MaxKeepDuration: 7 * 24 * time.Hour, // 1 week
},
})
if err != nil {
return opts, errors.Wrap(err, "failed to create fscache")
}
manager, err := dockerfile.NewBuildManager(daemon, sm, buildCache, daemon.IDMappings())
if err != nil {
return opts, err
}
bb, err := buildbackend.NewBackend(daemon, manager, buildCache)
if err != nil {
return opts, errors.Wrap(err, "failed to create buildmanager")
}
return routerOptions{
sessionManager: sm,
buildBackend: bb,
buildCache: buildCache,
daemon: daemon,
}, nil
}
|
57aef3b4 |
func (cli *DaemonCli) reloadConfig() { |
db63f937 |
reload := func(config *config.Config) { |
4192fe9c |
|
2b045027 |
// Revalidate and reload the authorization plugins
if err := validateAuthzPlugins(config.AuthorizationPlugins, cli.d.PluginStore); err != nil {
logrus.Fatalf("Error validating authorization plugin: %v", err)
return
} |
4192fe9c |
cli.authzMiddleware.SetPlugins(config.AuthorizationPlugins)
|
57aef3b4 |
if err := cli.d.Reload(config); err != nil {
logrus.Errorf("Error reconfiguring the daemon: %v", err)
return
} |
4192fe9c |
|
57aef3b4 |
if config.IsValueSet("debug") { |
ce375503 |
debugEnabled := debug.IsEnabled() |
57aef3b4 |
switch {
case debugEnabled && !config.Debug: // disable debug |
ce375503 |
debug.Disable() |
57aef3b4 |
case config.Debug && !debugEnabled: // enable debug |
ce375503 |
debug.Enable() |
531f4122 |
} |
57aef3b4 |
|
531f4122 |
} |
459e58ff |
} |
57aef3b4 |
|
db63f937 |
if err := config.Reload(*cli.configFile, cli.flags, reload); err != nil { |
57aef3b4 |
logrus.Error(err)
}
}
func (cli *DaemonCli) stop() {
cli.api.Close() |
1b95590d |
} |
01724c1c |
|
531f4122 |
// shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case
// d.Shutdown() is waiting too long to kill container or worst it's
// blocked there |
cc703784 |
func shutdownDaemon(d *daemon.Daemon) {
shutdownTimeout := d.ShutdownTimeout() |
531f4122 |
ch := make(chan struct{})
go func() { |
b08f071e |
d.Shutdown() |
531f4122 |
close(ch)
}() |
cc703784 |
if shutdownTimeout < 0 {
<-ch
logrus.Debug("Clean shutdown succeeded")
return
} |
531f4122 |
select {
case <-ch: |
51462327 |
logrus.Debug("Clean shutdown succeeded") |
cc703784 |
case <-time.After(time.Duration(shutdownTimeout) * time.Second): |
531f4122 |
logrus.Error("Force shutdown daemon")
}
} |
677a6b35 |
|
9ff9a91a |
func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) { |
db63f937 |
conf := opts.daemonConfig |
fb833947 |
flags := opts.flags |
9ff9a91a |
conf.Debug = opts.Debug
conf.Hosts = opts.Hosts
conf.LogLevel = opts.LogLevel
conf.TLS = opts.TLS
conf.TLSVerify = opts.TLSVerify |
db63f937 |
conf.CommonTLSOptions = config.CommonTLSOptions{} |
677a6b35 |
|
9ff9a91a |
if opts.TLSOptions != nil {
conf.CommonTLSOptions.CAFile = opts.TLSOptions.CAFile
conf.CommonTLSOptions.CertFile = opts.TLSOptions.CertFile
conf.CommonTLSOptions.KeyFile = opts.TLSOptions.KeyFile |
677a6b35 |
}
|
e428c824 |
if conf.TrustKeyPath == "" {
conf.TrustKeyPath = filepath.Join(
getDaemonConfDir(conf.Root),
defaultTrustKeyFile)
}
|
df7a72cf |
if flags.Changed("graph") && flags.Changed("data-root") {
return nil, fmt.Errorf(`cannot specify both "--graph" and "--data-root" option`)
}
|
fb833947 |
if opts.configFile != "" { |
db63f937 |
c, err := config.MergeDaemonConfigurations(conf, flags, opts.configFile) |
677a6b35 |
if err != nil { |
9894576f |
if flags.Changed("config-file") || !os.IsNotExist(err) { |
9b47b7b1 |
return nil, fmt.Errorf("unable to configure the Docker daemon with file %s: %v", opts.configFile, err) |
677a6b35 |
}
}
// the merged configuration can be nil if the config file didn't exist.
// leave the current configuration as it is if when that happens.
if c != nil { |
db63f937 |
conf = c |
677a6b35 |
}
}
|
db63f937 |
if err := config.Validate(conf); err != nil { |
7b2e5216 |
return nil, err
}
|
f7f101d5 |
if !conf.V2Only { |
12828001 |
logrus.Warnf(`The "disable-legacy-registry" option is deprecated and wil be removed in Docker v17.12. Interacting with legacy (v1) registries will no longer be supported in Docker v17.12"`)
}
|
df7a72cf |
if flags.Changed("graph") { |
12828001 |
logrus.Warnf(`The "-g / --graph" flag is deprecated. Please use "--data-root" instead`) |
df7a72cf |
}
|
e4c9079d |
// Labels of the docker engine used to allow multiple values associated with the same key.
// This is deprecated in 1.13, and, be removed after 3 release cycles.
// The following will check the conflict of labels, and report a warning for deprecation.
// |
5a9cee7b |
// TODO: After 3 release cycles (17.12) an error will be returned, and labels will be |
e4c9079d |
// sanitized to consolidate duplicate key-value pairs (config.Labels = newLabels):
//
// newLabels, err := daemon.GetConflictFreeLabels(config.Labels)
// if err != nil {
// return nil, err
// }
// config.Labels = newLabels
// |
db63f937 |
if _, err := config.GetConflictFreeLabels(conf.Labels); err != nil { |
e4c9079d |
logrus.Warnf("Engine labels with duplicate keys and conflicting values have been deprecated: %s", err)
}
|
cd344697 |
// Regardless of whether the user sets it to true or false, if they
// specify TLSVerify at all then we need to turn on TLS |
9ff9a91a |
if conf.IsValueSet(FlagTLSVerify) { |
db63f937 |
conf.TLS = true |
cd344697 |
}
// ensure that the log level is the one set after merging configurations |
9ff9a91a |
setLogLevel(conf.LogLevel) |
cd344697 |
|
db63f937 |
return conf, nil |
677a6b35 |
} |
1af76ef5 |
|
8f68adfa |
func initRouter(opts routerOptions) { |
f0d26e16 |
decoder := runconfig.ContainerDecoder{}
|
3976a33c |
routers := []router.Router{
// we need to add the checkpoint router before the container router or the DELETE gets masked |
8f68adfa |
checkpointrouter.NewRouter(opts.daemon, decoder),
container.NewRouter(opts.daemon, decoder),
image.NewRouter(opts.daemon, decoder),
systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildCache),
volume.NewRouter(opts.daemon),
build.NewRouter(opts.buildBackend, opts.daemon),
sessionrouter.NewRouter(opts.sessionManager),
swarmrouter.NewRouter(opts.cluster),
pluginrouter.NewRouter(opts.daemon.PluginManager()),
distributionrouter.NewRouter(opts.daemon), |
3976a33c |
} |
0dfbf960 |
|
8f68adfa |
if opts.daemon.NetworkControllerEnabled() {
routers = append(routers, network.NewRouter(opts.daemon, opts.cluster)) |
a793564b |
}
|
8f68adfa |
if opts.daemon.HasExperimental() { |
3976a33c |
for _, r := range routers {
for _, route := range r.Routes() {
if experimental, ok := route.(router.ExperimentalRoute); ok {
experimental.Enable()
}
}
}
}
|
408c7ade |
opts.api.InitRouter(routers...) |
1af76ef5 |
} |
8d346762 |
|
8f68adfa |
// TODO: remove this from cli and return the authzMiddleware |
709bf8b7 |
func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config, pluginStore plugingetter.PluginGetter) error { |
7534f172 |
v := cfg.Version |
8d346762 |
|
38de272b |
exp := middleware.NewExperimentalMiddleware(cli.Config.Experimental) |
7781a1bf |
s.UseMiddleware(exp)
|
8d346762 |
vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, api.MinVersion)
s.UseMiddleware(vm)
|
7d4eab55 |
if cfg.CorsHeaders != "" { |
8d346762 |
c := middleware.NewCORSMiddleware(cfg.CorsHeaders)
s.UseMiddleware(c)
}
|
38de272b |
cli.authzMiddleware = authorization.NewMiddleware(cli.Config.AuthorizationPlugins, pluginStore)
cli.Config.AuthzMiddleware = cli.authzMiddleware |
4192fe9c |
s.UseMiddleware(cli.authzMiddleware) |
2b045027 |
return nil
}
// validates that the plugins requested with the --authorization-plugin flag are valid AuthzDriver
// plugins present on the host and available to the daemon
func validateAuthzPlugins(requestedPlugins []string, pg plugingetter.PluginGetter) error {
for _, reqPlugin := range requestedPlugins { |
42860010 |
if _, err := pg.Get(reqPlugin, authorization.AuthZApiImplements, plugingetter.Lookup); err != nil { |
2b045027 |
return err
}
}
return nil |
8d346762 |
} |