From eafae0073a0c2122063913e844fcf5593c9b6ead Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Mon, 4 Jan 2021 09:40:36 +0100 Subject: [PATCH] registry: allow creating new and using existing registries --- cmd/registry/registryCreate.go | 2 +- pkg/client/cluster.go | 22 ++++++++++-- pkg/client/registry.go | 64 ++++++++++++++++++++++++++++++++-- pkg/config/transform.go | 11 ++++++ pkg/types/types.go | 10 ++++++ pkg/util/registry.go | 60 +++++++++++++++++++++++++++++++ 6 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 pkg/util/registry.go diff --git a/cmd/registry/registryCreate.go b/cmd/registry/registryCreate.go index 404969d8..d5a3e23b 100644 --- a/cmd/registry/registryCreate.go +++ b/cmd/registry/registryCreate.go @@ -62,7 +62,7 @@ func NewCmdRegistryCreate() *cobra.Command { if err != nil { log.Fatalln(err) } - if err := client.RegistryConnect(cmd.Context(), runtimes.SelectedRuntime, regNode, clusters); err != nil { + if err := client.RegistryConnectClusters(cmd.Context(), runtimes.SelectedRuntime, regNode, clusters); err != nil { log.Errorln(err) } }, diff --git a/pkg/client/cluster.go b/pkg/client/cluster.go index c569b5ad..19318d0c 100644 --- a/pkg/client/cluster.go +++ b/pkg/client/cluster.go @@ -137,17 +137,33 @@ func ClusterPrep(ctx context.Context, runtime k3drt.Runtime, clusterConfig *conf * Step 3: Registries */ + // Ensure referenced registries + for _, reg := range clusterConfig.ClusterCreateOpts.Registries.Use { + regNode, err := runtime.GetNode(ctx, &k3d.Node{Name: reg.Host}) + if err != nil { + return fmt.Errorf("Failed to find registry node '%s': %+v", reg.Host, err) + } + regFromNode, err := RegistryFromNode(regNode) + if err != nil { + return err + } + *reg = *regFromNode + } + // Create managed registry bound to this cluster if clusterConfig.ClusterCreateOpts.Registries.Create != nil { - if _, err := RegistryCreate(ctx, runtime, clusterConfig.ClusterCreateOpts.Registries.Create); err != nil { + registryNode, err := RegistryCreate(ctx, runtime, clusterConfig.ClusterCreateOpts.Registries.Create) + if err != nil { return fmt.Errorf("Failed to create registry: %+v", err) } + clusterConfig.Cluster.Nodes = append(clusterConfig.Cluster.Nodes, registryNode) + clusterConfig.ClusterCreateOpts.Registries.Use = append(clusterConfig.ClusterCreateOpts.Registries.Use, clusterConfig.ClusterCreateOpts.Registries.Create) } // Use existing registries (including the new one, if created) - log.Debugf("Using Registries: %+v", clusterConfig.ClusterCreateOpts.Registries.Use) + log.Tracef("Using Registries: %+v", clusterConfig.ClusterCreateOpts.Registries.Use) if len(clusterConfig.ClusterCreateOpts.Registries.Use) > 0 { // ensure that all selected registries exist and connect them to the cluster network @@ -156,7 +172,7 @@ func ClusterPrep(ctx context.Context, runtime k3drt.Runtime, clusterConfig *conf if err != nil { return fmt.Errorf("Failed to find registry node '%s': %+v", externalReg.Host, err) } - if err := RegistryConnect(ctx, runtime, regNode, []*k3d.Cluster{&clusterConfig.Cluster}); err != nil { + if err := RegistryConnectNetworks(ctx, runtime, regNode, []string{clusterConfig.Cluster.Network.Name}); err != nil { return fmt.Errorf("Failed to connect registry node '%s' to cluster network: %+v", regNode.Name, err) } } diff --git a/pkg/client/registry.go b/pkg/client/registry.go index 0c4e6af0..f77024c6 100644 --- a/pkg/client/registry.go +++ b/pkg/client/registry.go @@ -99,8 +99,8 @@ func RegistryCreate(ctx context.Context, runtime runtimes.Runtime, reg *k3d.Regi } -// RegistryConnect connects an existing registry to one or more clusters -func RegistryConnect(ctx context.Context, runtime runtimes.Runtime, registryNode *k3d.Node, clusters []*k3d.Cluster) error { +// RegistryConnectClusters connects an existing registry to one or more clusters +func RegistryConnectClusters(ctx context.Context, runtime runtimes.Runtime, registryNode *k3d.Node, clusters []*k3d.Cluster) error { // find registry node registryNode, err := NodeGet(ctx, runtime, registryNode) @@ -114,7 +114,7 @@ func RegistryConnect(ctx context.Context, runtime runtimes.Runtime, registryNode for _, c := range clusters { cluster, err := ClusterGet(ctx, runtime, c) if err != nil { - log.Warnf("Failed to connect to cluster '%s': Cluster not found", cluster.Name) + log.Warnf("Failed to connect to cluster '%s': Cluster not found", c.Name) failed++ continue } @@ -132,6 +132,33 @@ func RegistryConnect(ctx context.Context, runtime runtimes.Runtime, registryNode return nil } +// RegistryConnectNetworks connects an existing registry to one or more networks +func RegistryConnectNetworks(ctx context.Context, runtime runtimes.Runtime, registryNode *k3d.Node, networks []string) error { + + // find registry node + registryNode, err := NodeGet(ctx, runtime, registryNode) + if err != nil { + log.Errorf("Failed to find registry node '%s'", registryNode.Name) + return err + } + + // get cluster details and connect + failed := 0 + for _, net := range networks { + if err := runtime.ConnectNodeToNetwork(ctx, registryNode, net); err != nil { + log.Warnf("Failed to connect to network '%s': Connection failed", net) + log.Warnln(err) + failed++ + } + } + + if failed > 0 { + return fmt.Errorf("Failed to connect to one or more networks") + } + + return nil +} + // RegistryGenerateK3sConfig generates the k3s specific registries.yaml configuration for multiple registries func RegistryGenerateK3sConfig(ctx context.Context, registries []*k3d.Registry) (*k3s.Registry, error) { regConf := &k3s.Registry{} @@ -178,3 +205,34 @@ func RegistryGet(ctx context.Context, runtime runtimes.Runtime, name string) (*k return registry, nil } + +// RegistryFromNode transforms a node spec to a registry spec +func RegistryFromNode(node *k3d.Node) (*k3d.Registry, error) { + registry := &k3d.Registry{ + Host: node.Name, + Image: node.Image, + } + + // we expect exactly one portmap + if len(node.Ports) != 1 { + return nil, fmt.Errorf("Failed to parse registry spec from node %+v: 0 or multiple ports defined, where one is expected", node) + } + + for port, bindings := range node.Ports { + registry.ExposureOpts.Port = port + + // we expect 0 or 1 binding for that port + if len(bindings) > 1 { + return nil, fmt.Errorf("Failed to parse registry spec from node %+v: Multiple bindings '%+v' specified for port '%s' where one is expected", node, bindings, port) + } + + for _, binding := range bindings { + registry.ExposureOpts.Binding = binding + } + } + + log.Tracef("Got registry %+v from node %+v", registry, node) + + return registry, nil + +} diff --git a/pkg/config/transform.go b/pkg/config/transform.go index dd4c5db9..cbdf7ff1 100644 --- a/pkg/config/transform.go +++ b/pkg/config/transform.go @@ -33,6 +33,8 @@ import ( k3d "github.com/rancher/k3d/v4/pkg/types" "github.com/rancher/k3d/v4/pkg/util" "github.com/rancher/k3d/v4/version" + + log "github.com/sirupsen/logrus" ) // TransformSimpleToClusterConfig transforms a simple configuration to a full-fledged cluster configuration @@ -235,6 +237,15 @@ func TransformSimpleToClusterConfig(ctx context.Context, runtime runtimes.Runtim } } + for _, usereg := range simpleConfig.Registries.Use { + reg, err := util.ParseRegistryRef(usereg) + if err != nil { + return nil, fmt.Errorf("Failed to parse use-registry string '%s': %+v", usereg, err) + } + log.Tracef("Parsed registry reference: %+v", reg) + clusterCreateOpts.Registries.Use = append(clusterCreateOpts.Registries.Use, reg) + } + /********************** * Kubeconfig Options * **********************/ diff --git a/pkg/types/types.go b/pkg/types/types.go index 9df61557..114fb3b6 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -61,6 +61,7 @@ var ReadyLogMessageByRole = map[Role]string{ ServerRole: "k3s is up and running", AgentRole: "Successfully registered node", LoadBalancerRole: "start worker processes", + RegistryRole: "listening on", } // NodeWaitForLogMessageRestartWarnTime is the time after which to warn about a restarting container @@ -372,3 +373,12 @@ type Registry struct { } `yaml:"proxy,omitempty" json:"proxy,omitempty"` } `yaml:"options,omitempty" json:"options,omitempty"` } + +// RegistryExternal describes a minimal spec for an "external" registry +// "external" meaning, that it's unrelated to the current cluster +// e.g. used for the --registry-use flag registry reference +type RegistryExternal struct { + Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"` // default: http + Host string `yaml:"host" json:"host"` + Port string `yaml:"port" json:"port"` +} diff --git a/pkg/util/registry.go b/pkg/util/registry.go new file mode 100644 index 00000000..b524a8f8 --- /dev/null +++ b/pkg/util/registry.go @@ -0,0 +1,60 @@ +/* +Copyright © 2020 The k3d Author(s) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package util + +import ( + "fmt" + "regexp" + + "github.com/docker/go-connections/nat" + k3d "github.com/rancher/k3d/v4/pkg/types" +) + +var registryRefRegexp = regexp.MustCompile(`^(?Phttp:\/\/|https:\/\/)?(?P(?P\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?P[a-zA-Z\-\.0-9]+)){1}?((:)(?P\d{1,5}))?((:)(?P\d{1,5}))?$`) + +// ParseRegistryRef returns a registry struct parsed from a simplified definition string +func ParseRegistryRef(registryRef string) (*k3d.Registry, error) { + match := registryRefRegexp.FindStringSubmatch(registryRef) + + if len(match) == 0 { + return nil, fmt.Errorf("Failed to parse registry reference %s: Must be [proto://]host[:port]", registryRef) + } + + submatches := MapSubexpNames(registryRefRegexp.SubexpNames(), match) + + registry := &k3d.Registry{ + Host: submatches["hostref"], + Protocol: submatches["protocol"], + ExposureOpts: k3d.ExposureOpts{}, + } + registry.ExposureOpts.Host = submatches["hostref"] + + if submatches["port"] != "" { + registry.ExposureOpts.PortMapping = nat.PortMapping{ + Port: nat.Port(fmt.Sprintf("%s/tcp", submatches["internalport"])), + Binding: nat.PortBinding{ + HostPort: submatches["externalport"], + }, + } + } + return registry, nil +}