Skip to content

Commit 7071129

Browse files
authored
[Enhancement] More powerful registry-create opt (#727)
- `--registry-create NAME[:HOST][:HOSTPORT]` changed from bool flag - respective config added to config file
1 parent 149dfdb commit 7071129

15 files changed

+235
-34
lines changed

Diff for: cmd/cluster/clusterCreate.go

+23-4
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ func NewCmdClusterCreate() *cobra.Command {
265265
cmd.Flags().StringArrayP("runtime-label", "", nil, "Add label to container runtime (Format: `KEY[=VALUE][@NODEFILTER[;NODEFILTER...]]`\n - Example: `k3d cluster create --agents 2 --runtime-label \"my.label@agent:0,1\" --runtime-label \"other.label=somevalue@server:0\"`")
266266
_ = ppViper.BindPFlag("cli.runtime-labels", cmd.Flags().Lookup("runtime-label"))
267267

268+
cmd.Flags().String("registry-create", "", "Create a k3d-managed registry and connect it to the cluster (Format: `NAME[:HOST][:HOSTPORT]`\n - Example: `k3d cluster create --registry-create mycluster-registry:0.0.0.0:5432`")
269+
_ = ppViper.BindPFlag("cli.registries.create", cmd.Flags().Lookup("registry-create"))
270+
268271
/* k3s */
269272
cmd.Flags().StringArray("k3s-arg", nil, "Additional args passed to k3s command (Format: `ARG@NODEFILTER[;@NODEFILTER]`)\n - Example: `k3d cluster create --k3s-arg \"--disable=traefik@server:0\"")
270273
_ = cfgViper.BindPFlag("cli.k3sargs", cmd.Flags().Lookup("k3s-arg"))
@@ -334,9 +337,6 @@ func NewCmdClusterCreate() *cobra.Command {
334337
cmd.Flags().StringArray("registry-use", nil, "Connect to one or more k3d-managed registries running locally")
335338
_ = cfgViper.BindPFlag("registries.use", cmd.Flags().Lookup("registry-use"))
336339

337-
cmd.Flags().Bool("registry-create", false, "Create a k3d-managed registry and connect it to the cluster")
338-
_ = cfgViper.BindPFlag("registries.create", cmd.Flags().Lookup("registry-create"))
339-
340340
cmd.Flags().String("registry-config", "", "Specify path to an extra registries.yaml file")
341341
_ = cfgViper.BindPFlag("registries.config", cmd.Flags().Lookup("registry-config"))
342342
if err := cmd.MarkFlagFilename("registry-config", "yaml", "yml"); err != nil {
@@ -418,7 +418,7 @@ func applyCLIOverrides(cfg conf.SimpleConfig) (conf.SimpleConfig, error) {
418418
l.Log().Fatalln(err)
419419
}
420420

421-
if strings.Contains(volume, k3d.DefaultRegistriesFilePath) && (cfg.Registries.Create || cfg.Registries.Config != "" || len(cfg.Registries.Use) != 0) {
421+
if strings.Contains(volume, k3d.DefaultRegistriesFilePath) && (cfg.Registries.Create != nil || cfg.Registries.Config != "" || len(cfg.Registries.Use) != 0) {
422422
l.Log().Warnf("Seems like you're mounting a file at '%s' while also using a referenced registries config or k3d-managed registries: Your mounted file will probably be overwritten!", k3d.DefaultRegistriesFilePath)
423423
}
424424

@@ -576,5 +576,24 @@ func applyCLIOverrides(cfg conf.SimpleConfig) (conf.SimpleConfig, error) {
576576
})
577577
}
578578

579+
// --registry-create
580+
if ppViper.IsSet("cli.registries.create") {
581+
flagvalue := ppViper.GetString("cli.registries.create")
582+
fvSplit := strings.SplitN(flagvalue, ":", 2)
583+
if cfg.Registries.Create == nil {
584+
cfg.Registries.Create = &conf.SimpleConfigRegistryCreateConfig{}
585+
}
586+
cfg.Registries.Create.Name = fvSplit[0]
587+
if len(fvSplit) > 1 {
588+
exposeAPI, err = cliutil.ParsePortExposureSpec(fvSplit[1], "1234") // internal port is unused after all
589+
if err != nil {
590+
return cfg, fmt.Errorf("failed to registry port spec: %w", err)
591+
}
592+
cfg.Registries.Create.Host = exposeAPI.Host
593+
cfg.Registries.Create.HostPort = exposeAPI.Binding.HostPort
594+
}
595+
596+
}
597+
579598
return cfg, nil
580599
}

Diff for: docs/usage/configfile.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ env:
7777
nodeFilters:
7878
- server:0
7979
registries: # define how registries should be created or used
80-
create: true # creates a default registry to be used with the cluster; same as `--registry-create`
80+
create:
81+
name: registry.localhost # creates a default registry to be used with the cluster; same as `--registry-create registry.localhost`
8182
use:
8283
- k3d-myotherregistry:5000 # some other k3d-managed registry; same as `--registry-use 'k3d-myotherregistry:5000'`
8384
config: | # define contents of the `registries.yaml` file (or reference a file); same as `--registry-config /path/to/config.yaml`

Diff for: docs/usage/guides/registries.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,16 @@ name: test
3535
servers: 1
3636
agents: 2
3737
registries:
38-
create: true
38+
create:
39+
name: myregistry
3940
config: |
4041
mirrors:
4142
"my.company.registry":
4243
endpoint:
4344
- http://my.company.registry:5000
4445
```
4546

46-
Here, the config for the k3d-managed registry, created by the `create: true` flag will be merged with the config specified under `config: |`.
47+
Here, the config for the k3d-managed registry, created by the `create: {...}` option will be merged with the config specified under `config: |`.
4748

4849
### Authenticated registries
4950

@@ -100,14 +101,14 @@ k3d cluster create \
100101

101102
#### Create a dedicated registry together with your cluster
102103

103-
1. `#!bash k3d cluster create mycluster --registry-create`: This creates your cluster `mycluster` together with a registry container called `k3d-mycluster-registry`
104+
1. `#!bash k3d cluster create mycluster --registry-create mycluster-registry`: This creates your cluster `mycluster` together with a registry container called `mycluster-registry`
104105

105106
- k3d sets everything up in the cluster for containerd to be able to pull images from that registry (using the `registries.yaml` file)
106107
- the port, which the registry is listening on will be mapped to a random port on your host system
107108

108-
2. Check the k3d command output or `#!bash docker ps -f name=k3d-mycluster-registry` to find the exposed port (let's use `12345` here)
109-
3. Pull some image (optional) `#!bash docker pull alpine:latest`, re-tag it to reference your newly created registry `#!bash docker tag alpine:latest k3d-mycluster-registry:12345/testimage:local` and push it `#!bash docker push k3d-mycluster-registry:12345/testimage:local`
110-
4. Use kubectl to create a new pod in your cluster using that image to see, if the cluster can pull from the new registry: `#!bash kubectl run --image k3d-mycluster-registry:12345/testimage:local testimage --command -- tail -f /dev/null` (creates a container that will not do anything but keep on running)
109+
2. Check the k3d command output or `#!bash docker ps -f name=mycluster-registry` to find the exposed port (let's use `12345` here)
110+
3. Pull some image (optional) `#!bash docker pull alpine:latest`, re-tag it to reference your newly created registry `#!bash docker tag alpine:latest mycluster-registry:12345/testimage:local` and push it `#!bash docker push mycluster-registry:12345/testimage:local`
111+
4. Use kubectl to create a new pod in your cluster using that image to see, if the cluster can pull from the new registry: `#!bash kubectl run --image mycluster-registry:12345/testimage:local testimage --command -- tail -f /dev/null` (creates a container that will not do anything but keep on running)
111112

112113
#### Create a customized k3d-managed registry
113114

Diff for: pkg/config/config_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,52 @@ func TestReadUnknownConfig(t *testing.T) {
256256
}
257257

258258
}
259+
260+
func TestReadSimpleConfigRegistries(t *testing.T) {
261+
262+
exposedAPI := conf.SimpleExposureOpts{}
263+
exposedAPI.HostIP = "0.0.0.0"
264+
exposedAPI.HostPort = "6443"
265+
266+
expectedConfig := conf.SimpleConfig{
267+
TypeMeta: configtypes.TypeMeta{
268+
APIVersion: "k3d.io/v1alpha3",
269+
Kind: "Simple",
270+
},
271+
Name: "test",
272+
Servers: 1,
273+
Agents: 1,
274+
Registries: conf.SimpleConfigRegistries{
275+
Create: &conf.SimpleConfigRegistryCreateConfig{
276+
Name: "registry.localhost",
277+
Host: "0.0.0.0",
278+
HostPort: "5001",
279+
},
280+
},
281+
}
282+
283+
cfgFile := "./test_assets/config_test_registries.yaml"
284+
285+
config := viper.New()
286+
config.SetConfigFile(cfgFile)
287+
288+
// try to read config into memory (viper map structure)
289+
if err := config.ReadInConfig(); err != nil {
290+
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
291+
t.Error(err)
292+
}
293+
// config file found but some other error happened
294+
t.Error(err)
295+
}
296+
297+
readConfig, err := FromViper(config)
298+
if err != nil {
299+
t.Error(err)
300+
}
301+
302+
t.Logf("\n========== Read Config ==========\n%+v\n=================================\n", readConfig)
303+
304+
if diff := deep.Equal(readConfig, expectedConfig); diff != nil {
305+
t.Errorf("Actual representation\n%+v\ndoes not match expected representation\n%+v\nDiff:\n%+v", readConfig, expectedConfig, diff)
306+
}
307+
}

Diff for: pkg/config/test_assets/config_test_registries.yaml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: k3d.io/v1alpha3
2+
kind: Simple
3+
name: test
4+
servers: 1
5+
agents: 1
6+
registries:
7+
create:
8+
name: registry.localhost
9+
host: "0.0.0.0"
10+
hostPort: "5001"

Diff for: pkg/config/transform.go

+20-3
Original file line numberDiff line numberDiff line change
@@ -284,14 +284,31 @@ func TransformSimpleToClusterConfig(ctx context.Context, runtime runtimes.Runtim
284284
/*
285285
* Registries
286286
*/
287-
if simpleConfig.Registries.Create {
288-
regPort, err := cliutil.ParsePortExposureSpec("random", k3d.DefaultRegistryPort)
287+
if simpleConfig.Registries.Create != nil {
288+
289+
epSpecHost := "0.0.0.0"
290+
epSpecPort := "random"
291+
292+
if simpleConfig.Registries.Create.HostPort != "" {
293+
epSpecPort = simpleConfig.Registries.Create.HostPort
294+
}
295+
if simpleConfig.Registries.Create.Host != "" {
296+
epSpecHost = simpleConfig.Registries.Create.Host
297+
}
298+
299+
regPort, err := cliutil.ParsePortExposureSpec(fmt.Sprintf("%s:%s", epSpecHost, epSpecPort), k3d.DefaultRegistryPort)
289300
if err != nil {
290301
return nil, fmt.Errorf("failed to get port for registry: %w", err)
291302
}
303+
304+
regName := fmt.Sprintf("%s-%s-registry", k3d.DefaultObjectNamePrefix, newCluster.Name)
305+
if simpleConfig.Registries.Create.Name != "" {
306+
regName = simpleConfig.Registries.Create.Name
307+
}
308+
292309
clusterCreateOpts.Registries.Create = &k3d.Registry{
293310
ClusterRef: newCluster.Name,
294-
Host: fmt.Sprintf("%s-%s-registry", k3d.DefaultObjectNamePrefix, newCluster.Name),
311+
Host: regName,
295312
Image: fmt.Sprintf("%s:%s", k3d.DefaultRegistryImageRepo, k3d.DefaultRegistryImageTag),
296313
ExposureOpts: *regPort,
297314
}

Diff for: pkg/config/v1alpha3/migrations.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ package v1alpha3
2424

2525
import (
2626
"encoding/json"
27+
"fmt"
2728

2829
configtypes "github.com/rancher/k3d/v5/pkg/config/types"
2930
"github.com/rancher/k3d/v5/pkg/config/v1alpha2"
3031
l "github.com/rancher/k3d/v5/pkg/logger"
32+
k3d "github.com/rancher/k3d/v5/pkg/types"
3133
)
3234

3335
var Migrations = map[string]func(configtypes.Config) (configtypes.Config, error){
@@ -43,9 +45,18 @@ func MigrateV1Alpha2(input configtypes.Config) (configtypes.Config, error) {
4345
}
4446

4547
if input.GetKind() == "Simple" {
46-
cfg := SimpleConfig{}
48+
cfgIntermediate := SimpleConfigIntermediateV1alpha2{}
4749

48-
if err := json.Unmarshal(injson, &cfg); err != nil {
50+
if err := json.Unmarshal(injson, &cfgIntermediate); err != nil {
51+
return nil, err
52+
}
53+
54+
intermediateJSON, err := json.Marshal(cfgIntermediate)
55+
if err != nil {
56+
return nil, err
57+
}
58+
cfg := SimpleConfig{}
59+
if err := json.Unmarshal(intermediateJSON, &cfg); err != nil {
4960
return nil, err
5061
}
5162

@@ -78,6 +89,14 @@ func MigrateV1Alpha2(input configtypes.Config) (configtypes.Config, error) {
7889
})
7990
}
8091

92+
if input.(v1alpha2.SimpleConfig).Registries.Create {
93+
cfg.Registries.Create = &SimpleConfigRegistryCreateConfig{
94+
Name: fmt.Sprintf("%s-%s-registry", k3d.DefaultObjectNamePrefix, cfg.Name),
95+
Host: "0.0.0.0",
96+
HostPort: "random",
97+
}
98+
}
99+
81100
cfg.APIVersion = ApiVersion
82101

83102
l.Log().Debugf("Migrated config: %+v", cfg)

Diff for: pkg/config/v1alpha3/schema.json

+49-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,55 @@
240240
}
241241
},
242242
"registries": {
243-
"type": "object"
243+
"type": "object",
244+
"properties": {
245+
"create": {
246+
"type": "object",
247+
"description": "Create a new container image registry alongside the cluster.",
248+
"properties": {
249+
"name": {
250+
"type": "string",
251+
"examples": [
252+
"myregistry",
253+
"registry.localhost"
254+
]
255+
},
256+
"host": {
257+
"type": "string",
258+
"examples": [
259+
"0.0.0.0",
260+
"localhost",
261+
"127.0.0.1"
262+
],
263+
"default": "0.0.0.0"
264+
},
265+
"hostPort": {
266+
"type": "string",
267+
"examples": [
268+
"5000",
269+
"2345"
270+
],
271+
"default": "random"
272+
}
273+
},
274+
"additionalProperties": false
275+
},
276+
"use": {
277+
"type": "array",
278+
"description": "Connect another container image registry to the cluster.",
279+
"items": {
280+
"type": "string"
281+
},
282+
"examples": [
283+
"otherregistry:5000"
284+
]
285+
},
286+
"config": {
287+
"type": "string",
288+
"description": "Reference a K3s registry configuration file or at it's contents here."
289+
},
290+
"additionalProperties": false
291+
}
244292
}
245293
},
246294
"additionalProperties": false,

Diff for: pkg/config/v1alpha3/types.go

+36-5
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ type K3sArgWithNodeFilters struct {
8181
NodeFilters []string `mapstructure:"nodeFilters" yaml:"nodeFilters" json:"nodeFilters,omitempty"`
8282
}
8383

84+
type SimpleConfigRegistryCreateConfig struct {
85+
Name string `mapstructure:"name" yaml:"name" json:"name"`
86+
Host string `mapstructure:"host" yaml:"host" json:"host"`
87+
HostPort string `mapstructure:"hostPort" yaml:"hostPort" json:"hostPort"`
88+
}
89+
8490
// SimpleConfigOptionsKubeconfig describes the set of options referring to the kubeconfig during cluster creation.
8591
type SimpleConfigOptionsKubeconfig struct {
8692
UpdateDefaultKubeconfig bool `mapstructure:"updateDefaultKubeconfig" yaml:"updateDefaultKubeconfig" json:"updateDefaultKubeconfig,omitempty"` // default: true
@@ -120,6 +126,18 @@ type SimpleConfigOptionsK3s struct {
120126
NodeLabels []LabelWithNodeFilters `mapstructure:"nodeLabels" yaml:"nodeLabels"`
121127
}
122128

129+
type SimpleConfigRegistries struct {
130+
Use []string `mapstructure:"use" yaml:"use,omitempty" json:"use,omitempty"`
131+
Create *SimpleConfigRegistryCreateConfig `mapstructure:"create" yaml:"create,omitempty" json:"create,omitempty"`
132+
Config string `mapstructure:"config" yaml:"config,omitempty" json:"config,omitempty"` // registries.yaml (k3s config for containerd registry override)
133+
}
134+
135+
type SimpleConfigRegistriesIntermediateV1alpha2 struct {
136+
Use []string `mapstructure:"use" yaml:"use,omitempty" json:"use,omitempty"`
137+
// Field "Create" changed significantly, so it's dropped here
138+
Config string `mapstructure:"config" yaml:"config,omitempty" json:"config,omitempty"` // registries.yaml (k3s config for containerd registry override)
139+
}
140+
123141
// SimpleConfig describes the toplevel k3d configuration file.
124142
type SimpleConfig struct {
125143
config.TypeMeta `mapstructure:",squash" yaml:",inline"`
@@ -135,11 +153,24 @@ type SimpleConfig struct {
135153
Ports []PortWithNodeFilters `mapstructure:"ports" yaml:"ports" json:"ports,omitempty"`
136154
Options SimpleConfigOptions `mapstructure:"options" yaml:"options" json:"options,omitempty"`
137155
Env []EnvVarWithNodeFilters `mapstructure:"env" yaml:"env" json:"env,omitempty"`
138-
Registries struct {
139-
Use []string `mapstructure:"use" yaml:"use,omitempty" json:"use,omitempty"`
140-
Create bool `mapstructure:"create" yaml:"create,omitempty" json:"create,omitempty"`
141-
Config string `mapstructure:"config" yaml:"config,omitempty" json:"config,omitempty"` // registries.yaml (k3s config for containerd registry override)
142-
} `mapstructure:"registries" yaml:"registries,omitempty" json:"registries,omitempty"`
156+
Registries SimpleConfigRegistries `mapstructure:"registries" yaml:"registries,omitempty" json:"registries,omitempty"`
157+
}
158+
159+
type SimpleConfigIntermediateV1alpha2 struct {
160+
config.TypeMeta `mapstructure:",squash" yaml:",inline"`
161+
Name string `mapstructure:"name" yaml:"name" json:"name,omitempty"`
162+
Servers int `mapstructure:"servers" yaml:"servers" json:"servers,omitempty"` //nolint:lll // default 1
163+
Agents int `mapstructure:"agents" yaml:"agents" json:"agents,omitempty"` //nolint:lll // default 0
164+
ExposeAPI SimpleExposureOpts `mapstructure:"kubeAPI" yaml:"kubeAPI" json:"kubeAPI,omitempty"`
165+
Image string `mapstructure:"image" yaml:"image" json:"image,omitempty"`
166+
Network string `mapstructure:"network" yaml:"network" json:"network,omitempty"`
167+
Subnet string `mapstructure:"subnet" yaml:"subnet" json:"subnet,omitempty"`
168+
ClusterToken string `mapstructure:"token" yaml:"clusterToken" json:"clusterToken,omitempty"` // default: auto-generated
169+
Volumes []VolumeWithNodeFilters `mapstructure:"volumes" yaml:"volumes" json:"volumes,omitempty"`
170+
Ports []PortWithNodeFilters `mapstructure:"ports" yaml:"ports" json:"ports,omitempty"`
171+
Options SimpleConfigOptions `mapstructure:"options" yaml:"options" json:"options,omitempty"`
172+
Env []EnvVarWithNodeFilters `mapstructure:"env" yaml:"env" json:"env,omitempty"`
173+
Registries SimpleConfigRegistriesIntermediateV1alpha2 `mapstructure:"registries" yaml:"registries,omitempty" json:"registries,omitempty"`
143174
}
144175

145176
// SimpleExposureOpts provides a simplified syntax compared to the original k3d.ExposureOpts

Diff for: pkg/runtimes/docker/network.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func (d Docker) CreateNetworkIfNotPresent(ctx context.Context, inNet *k3d.Cluste
204204
return nil, false, fmt.Errorf("docker failed to inspect newly created network '%s': %w", newNet.ID, err)
205205
}
206206

207-
l.Log().Infof("Created network '%s' (%s)", inNet.Name, networkDetails.ID)
207+
l.Log().Infof("Created network '%s'", inNet.Name)
208208
prefix, err := netaddr.ParseIPPrefix(networkDetails.IPAM.Config[0].Subnet)
209209
if err != nil {
210210
return nil, false, fmt.Errorf("failed to parse IP Prefix of newly created network '%s': %w", newNet.ID, err)

Diff for: tests/assets/config_test_simple.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ env:
2323
nodeFilters:
2424
- all
2525
registries:
26-
create: true
26+
create:
27+
name: registry.localhost
2728
use: []
2829
config: |
2930
mirrors:

Diff for: tests/assets/config_test_simple_migration_v1alpha3.yaml

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ env:
2323
nodeFilters:
2424
- all
2525
registries:
26-
create: true
26+
create:
27+
name: k3d-test-registry
28+
host: "0.0.0.0"
29+
hostPort: random
2730
use: []
2831
config: |
2932
mirrors:

0 commit comments

Comments
 (0)