fix: don't use runtime-specs Mount struct in machine config

First of all, it breaks our backwards compatibility promises and breaks
documentation generation. Upstream `specs.Mount` might change at any
time.

The issue was that containerd 1.7.x brings in new `specs.Mount` which
contains extra fields which don't have `omitempty` for YAML, so
machinery always generates them which confuses old Talos versions.

Use a copy of the upstream struct with proper YAML tags, and also
provide a special trick to make sure if the upstream struct changes, we
have a chance to update our copy of the struct.

Also this fixes docs and JSON schema.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2023-10-11 22:48:18 +04:00
parent d1b27926c2
commit 7bb205ebe2
No known key found for this signature in database
GPG Key ID: FE042E3D4085A811
9 changed files with 412 additions and 57 deletions

View File

@ -96,11 +96,9 @@ func (suite *KubeletConfigSuite) TestReconcile() {
},
KubeletExtraMounts: []v1alpha1.ExtraMount{
{
Mount: specs.Mount{
Destination: "/tmp",
Source: "/var",
Type: "tmpfs",
},
Destination: "/tmp",
Source: "/var",
Type: "tmpfs",
},
},
KubeletExtraConfig: v1alpha1.Unstructured{

View File

@ -1353,7 +1353,59 @@
"type": "object"
},
"ExtraMount": {
"properties": {},
"properties": {
"destination": {
"type": "string",
"title": "destination",
"description": "Destination is the absolute path where the mount will be placed in the container.\n",
"markdownDescription": "Destination is the absolute path where the mount will be placed in the container.",
"x-intellij-html-description": "\u003cp\u003eDestination is the absolute path where the mount will be placed in the container.\u003c/p\u003e\n"
},
"type": {
"type": "string",
"title": "type",
"description": "Type specifies the mount kind.\n",
"markdownDescription": "Type specifies the mount kind.",
"x-intellij-html-description": "\u003cp\u003eType specifies the mount kind.\u003c/p\u003e\n"
},
"source": {
"type": "string",
"title": "source",
"description": "Source specifies the source path of the mount.\n",
"markdownDescription": "Source specifies the source path of the mount.",
"x-intellij-html-description": "\u003cp\u003eSource specifies the source path of the mount.\u003c/p\u003e\n"
},
"options": {
"items": {
"type": "string"
},
"type": "array",
"title": "options",
"description": "Options are fstab style mount options.\n",
"markdownDescription": "Options are fstab style mount options.",
"x-intellij-html-description": "\u003cp\u003eOptions are fstab style mount options.\u003c/p\u003e\n"
},
"uidMappings": {
"items": {
"$ref": "#/$defs/LinuxIDMapping"
},
"type": "array",
"title": "uidMappings",
"description": "UID/GID mappings used for changing file owners w/o calling chown, fs should support it.\n\nEvery mount point could have its own mapping.\n",
"markdownDescription": "UID/GID mappings used for changing file owners w/o calling chown, fs should support it.\n\nEvery mount point could have its own mapping.",
"x-intellij-html-description": "\u003cp\u003eUID/GID mappings used for changing file owners w/o calling chown, fs should support it.\u003c/p\u003e\n\n\u003cp\u003eEvery mount point could have its own mapping.\u003c/p\u003e\n"
},
"gidMappings": {
"items": {
"$ref": "#/$defs/LinuxIDMapping"
},
"type": "array",
"title": "gidMappings",
"description": "UID/GID mappings used for changing file owners w/o calling chown, fs should support it.\n\nEvery mount point could have its own mapping.\n",
"markdownDescription": "UID/GID mappings used for changing file owners w/o calling chown, fs should support it.\n\nEvery mount point could have its own mapping.",
"x-intellij-html-description": "\u003cp\u003eUID/GID mappings used for changing file owners w/o calling chown, fs should support it.\u003c/p\u003e\n\n\u003cp\u003eEvery mount point could have its own mapping.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
"type": "object"
},
@ -1771,6 +1823,33 @@
"additionalProperties": false,
"type": "object"
},
"LinuxIDMapping": {
"properties": {
"containerID": {
"type": "integer",
"title": "containerID",
"description": "ContainerID is the starting UID/GID in the container.\n",
"markdownDescription": "ContainerID is the starting UID/GID in the container.",
"x-intellij-html-description": "\u003cp\u003eContainerID is the starting UID/GID in the container.\u003c/p\u003e\n"
},
"hostID": {
"type": "integer",
"title": "hostID",
"description": "HostID is the starting UID/GID on the host to be mapped to ContainerID.\n",
"markdownDescription": "HostID is the starting UID/GID on the host to be mapped to 'ContainerID'.",
"x-intellij-html-description": "\u003cp\u003eHostID is the starting UID/GID on the host to be mapped to \u0026lsquo;ContainerID\u0026rsquo;.\u003c/p\u003e\n"
},
"size": {
"type": "integer",
"title": "size",
"description": "Size is the number of IDs to be mapped.\n",
"markdownDescription": "Size is the number of IDs to be mapped.",
"x-intellij-html-description": "\u003cp\u003eSize is the number of IDs to be mapped.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
"type": "object"
},
"LoggingConfig": {
"properties": {
"destinations": {

View File

@ -9,7 +9,6 @@ import (
"strings"
"time"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/siderolabs/crypto/x509"
"github.com/siderolabs/go-pointer"
"gopkg.in/yaml.v3"
@ -475,15 +474,13 @@ func clusterEndpointExample2() *Endpoint {
func kubeletExtraMountsExample() []ExtraMount {
return []ExtraMount{
{
specs.Mount{
Source: "/var/lib/example",
Destination: "/var/lib/example",
Type: "bind",
Options: []string{
"bind",
"rshared",
"rw",
},
Source: "/var/lib/example",
Destination: "/var/lib/example",
Type: "bind",
Options: []string{
"bind",
"rshared",
"rw",
},
},
}

View File

@ -383,7 +383,33 @@ func (k *KubeletConfig) ExtraArgs() map[string]string {
// ExtraMounts implements the config.Provider interface.
func (k *KubeletConfig) ExtraMounts() []specs.Mount {
return xslices.Map(k.KubeletExtraMounts, func(m ExtraMount) specs.Mount { return m.Mount })
// use the intermediate type which is assignable to specs.Mount so that
// we can be sure that `specs.Mount` and `Mount` have exactly same fields.
//
// as in Go []T1 is not assignable to []T2, even if T1 and T2 are assignable, we cannot
// use direct conversion of Mount and specs.Mount
type mountConverter struct {
Destination string
Type string
Source string
Options []string
UIDMappings []specs.LinuxIDMapping
GIDMappings []specs.LinuxIDMapping
}
return xslices.Map(k.KubeletExtraMounts,
func(m ExtraMount) specs.Mount {
return specs.Mount(func() mountConverter {
return mountConverter{
Destination: m.Destination,
Type: m.Type,
Source: m.Source,
Options: m.Options,
UIDMappings: xslices.Map(m.UIDMappings, func(m LinuxIDMapping) specs.LinuxIDMapping { return specs.LinuxIDMapping(m) }),
GIDMappings: xslices.Map(m.GIDMappings, func(m LinuxIDMapping) specs.LinuxIDMapping { return specs.LinuxIDMapping(m) }),
}
}())
})
}
// ExtraConfig implements the config.Provider interface.

View File

@ -27,7 +27,6 @@ import (
"time"
"github.com/dustin/go-humanize"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/siderolabs/crypto/x509"
"github.com/siderolabs/go-blockdevice/blockdevice/util/disk"
"gopkg.in/yaml.v3"
@ -492,26 +491,44 @@ type ClusterConfig struct {
AllowSchedulingOnControlPlanes *bool `yaml:"allowSchedulingOnControlPlanes,omitempty"`
}
// LinuxIDMapping represents the Linux ID mapping.
type LinuxIDMapping struct {
// description: |
// ContainerID is the starting UID/GID in the container.
ContainerID uint32 `yaml:"containerID"`
// description: |
// HostID is the starting UID/GID on the host to be mapped to 'ContainerID'.
HostID uint32 `yaml:"hostID"`
// description: |
// Size is the number of IDs to be mapped.
Size uint32 `yaml:"size"`
}
// ExtraMount wraps OCI Mount specification.
type ExtraMount struct {
specs.Mount `yaml:",inline"`
}
// description: |
// Destination is the absolute path where the mount will be placed in the container.
Destination string `yaml:"destination"`
// description: |
// Type specifies the mount kind.
Type string `yaml:"type,omitempty"`
// description: |
// Source specifies the source path of the mount.
Source string `yaml:"source,omitempty"`
// description: |
// Options are fstab style mount options.
Options []string `yaml:"options,omitempty"`
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExtraMount) DeepCopyInto(out *ExtraMount) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraMount.
func (in *ExtraMount) DeepCopy() *ExtraMount {
if in == nil {
return nil
}
out := new(ExtraMount)
in.DeepCopyInto(out)
return out
// description: |
// UID/GID mappings used for changing file owners w/o calling chown, fs should support it.
//
// Every mount point could have its own mapping.
UIDMappings []LinuxIDMapping `yaml:"uidMappings,omitempty"`
// description: |
// UID/GID mappings used for changing file owners w/o calling chown, fs should support it.
//
// Every mount point could have its own mapping.
GIDMappings []LinuxIDMapping `yaml:"gidMappings,omitempty"`
}
// MachineControlPlaneConfig machine specific configuration options.

View File

@ -545,6 +545,49 @@ func (ClusterConfig) Doc() *encoder.Doc {
return doc
}
func (LinuxIDMapping) Doc() *encoder.Doc {
doc := &encoder.Doc{
Type: "LinuxIDMapping",
Comments: [3]string{"" /* encoder.HeadComment */, "LinuxIDMapping represents the Linux ID mapping." /* encoder.LineComment */, "" /* encoder.FootComment */},
Description: "LinuxIDMapping represents the Linux ID mapping.",
AppearsIn: []encoder.Appearance{
{
TypeName: "ExtraMount",
FieldName: "uidMappings",
},
{
TypeName: "ExtraMount",
FieldName: "gidMappings",
},
},
Fields: []encoder.Doc{
{
Name: "containerID",
Type: "uint32",
Note: "",
Description: "ContainerID is the starting UID/GID in the container.",
Comments: [3]string{"" /* encoder.HeadComment */, "ContainerID is the starting UID/GID in the container." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "hostID",
Type: "uint32",
Note: "",
Description: "HostID is the starting UID/GID on the host to be mapped to 'ContainerID'.",
Comments: [3]string{"" /* encoder.HeadComment */, "HostID is the starting UID/GID on the host to be mapped to 'ContainerID'." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "size",
Type: "uint32",
Note: "",
Description: "Size is the number of IDs to be mapped.",
Comments: [3]string{"" /* encoder.HeadComment */, "Size is the number of IDs to be mapped." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
},
}
return doc
}
func (ExtraMount) Doc() *encoder.Doc {
doc := &encoder.Doc{
Type: "ExtraMount",
@ -556,7 +599,50 @@ func (ExtraMount) Doc() *encoder.Doc {
FieldName: "extraMounts",
},
},
Fields: []encoder.Doc{},
Fields: []encoder.Doc{
{
Name: "destination",
Type: "string",
Note: "",
Description: "Destination is the absolute path where the mount will be placed in the container.",
Comments: [3]string{"" /* encoder.HeadComment */, "Destination is the absolute path where the mount will be placed in the container." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "type",
Type: "string",
Note: "",
Description: "Type specifies the mount kind.",
Comments: [3]string{"" /* encoder.HeadComment */, "Type specifies the mount kind." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "source",
Type: "string",
Note: "",
Description: "Source specifies the source path of the mount.",
Comments: [3]string{"" /* encoder.HeadComment */, "Source specifies the source path of the mount." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "options",
Type: "[]string",
Note: "",
Description: "Options are fstab style mount options.",
Comments: [3]string{"" /* encoder.HeadComment */, "Options are fstab style mount options." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "uidMappings",
Type: "[]LinuxIDMapping",
Note: "",
Description: "UID/GID mappings used for changing file owners w/o calling chown, fs should support it.\n\nEvery mount point could have its own mapping.",
Comments: [3]string{"" /* encoder.HeadComment */, "UID/GID mappings used for changing file owners w/o calling chown, fs should support it." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "gidMappings",
Type: "[]LinuxIDMapping",
Note: "",
Description: "UID/GID mappings used for changing file owners w/o calling chown, fs should support it.\n\nEvery mount point could have its own mapping.",
Comments: [3]string{"" /* encoder.HeadComment */, "UID/GID mappings used for changing file owners w/o calling chown, fs should support it." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
},
}
doc.AddExample("", kubeletExtraMountsExample())
@ -3833,6 +3919,7 @@ func GetConfigurationDoc() *encoder.FileDoc {
MachineConfig{}.Doc(),
MachineSeccompProfile{}.Doc(),
ClusterConfig{}.Doc(),
LinuxIDMapping{}.Doc(),
ExtraMount{}.Doc(),
MachineControlPlaneConfig{}.Doc(),
MachineControllerManagerConfig{}.Doc(),

View File

@ -987,6 +987,37 @@ func (in *ExtraHost) DeepCopy() *ExtraHost {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ExtraMount) DeepCopyInto(out *ExtraMount) {
*out = *in
if in.Options != nil {
in, out := &in.Options, &out.Options
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.UIDMappings != nil {
in, out := &in.UIDMappings, &out.UIDMappings
*out = make([]LinuxIDMapping, len(*in))
copy(*out, *in)
}
if in.GIDMappings != nil {
in, out := &in.GIDMappings, &out.GIDMappings
*out = make([]LinuxIDMapping, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraMount.
func (in *ExtraMount) DeepCopy() *ExtraMount {
if in == nil {
return nil
}
out := new(ExtraMount)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FeaturesConfig) DeepCopyInto(out *FeaturesConfig) {
*out = *in
@ -1378,6 +1409,22 @@ func (in *KubernetesTalosAPIAccessConfig) DeepCopy() *KubernetesTalosAPIAccessCo
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LinuxIDMapping) DeepCopyInto(out *LinuxIDMapping) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinuxIDMapping.
func (in *LinuxIDMapping) DeepCopy() *LinuxIDMapping {
if in == nil {
return nil
}
out := new(LinuxIDMapping)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *LoggingConfig) DeepCopyInto(out *LoggingConfig) {
*out = *in

View File

@ -113,15 +113,14 @@ kubelet:
# # The `extraMounts` field is used to add additional mounts to the kubelet container.
# extraMounts:
# - destination: /var/lib/example
# type: bind
# source: /var/lib/example
# - destination: /var/lib/example # Destination is the absolute path where the mount will be placed in the container.
# type: bind # Type specifies the mount kind.
# source: /var/lib/example # Source specifies the source path of the mount.
# # Options are fstab style mount options.
# options:
# - bind
# - rshared
# - rw
# uidmappings: []
# gidmappings: []
# # The `extraConfig` field is used to provide kubelet configuration overrides.
# extraConfig:
@ -654,6 +653,26 @@ allowSchedulingOnControlPlanes: true
---
## LinuxIDMapping
LinuxIDMapping represents the Linux ID mapping.
Appears in:
- <code><a href="#extramount">ExtraMount</a>.uidMappings</code>
- <code><a href="#extramount">ExtraMount</a>.gidMappings</code>
| Field | Type | Description | Value(s) |
|-------|------|-------------|----------|
|`containerID` |uint32 |ContainerID is the starting UID/GID in the container. | |
|`hostID` |uint32 |HostID is the starting UID/GID on the host to be mapped to 'ContainerID'. | |
|`size` |uint32 |Size is the number of IDs to be mapped. | |
---
## ExtraMount
ExtraMount wraps OCI Mount specification.
@ -665,18 +684,26 @@ Appears in:
{{< highlight yaml >}}
- destination: /var/lib/example
type: bind
source: /var/lib/example
- destination: /var/lib/example # Destination is the absolute path where the mount will be placed in the container.
type: bind # Type specifies the mount kind.
source: /var/lib/example # Source specifies the source path of the mount.
# Options are fstab style mount options.
options:
- bind
- rshared
- rw
uidmappings: []
gidmappings: []
{{< /highlight >}}
| Field | Type | Description | Value(s) |
|-------|------|-------------|----------|
|`destination` |string |Destination is the absolute path where the mount will be placed in the container. | |
|`type` |string |Type specifies the mount kind. | |
|`source` |string |Source specifies the source path of the mount. | |
|`options` |[]string |Options are fstab style mount options. | |
|`uidMappings` |[]<a href="#linuxidmapping">LinuxIDMapping</a> |<details><summary>UID/GID mappings used for changing file owners w/o calling chown, fs should support it.</summary><br />Every mount point could have its own mapping.</details> | |
|`gidMappings` |[]<a href="#linuxidmapping">LinuxIDMapping</a> |<details><summary>UID/GID mappings used for changing file owners w/o calling chown, fs should support it.</summary><br />Every mount point could have its own mapping.</details> | |
---
@ -763,15 +790,14 @@ extraArgs:
# # The `extraMounts` field is used to add additional mounts to the kubelet container.
# extraMounts:
# - destination: /var/lib/example
# type: bind
# source: /var/lib/example
# - destination: /var/lib/example # Destination is the absolute path where the mount will be placed in the container.
# type: bind # Type specifies the mount kind.
# source: /var/lib/example # Source specifies the source path of the mount.
# # Options are fstab style mount options.
# options:
# - bind
# - rshared
# - rw
# uidmappings: []
# gidmappings: []
# # The `extraConfig` field is used to provide kubelet configuration overrides.
# extraConfig:
@ -803,15 +829,14 @@ extraArgs:
{{< /highlight >}}</details> | |
|`extraMounts` |[]<a href="#extramount">ExtraMount</a> |<details><summary>The `extraMounts` field is used to add additional mounts to the kubelet container.</summary>Note that either `bind` or `rbind` are required in the `options`.</details> <details><summary>Show example(s)</summary>{{< highlight yaml >}}
extraMounts:
- destination: /var/lib/example
type: bind
source: /var/lib/example
- destination: /var/lib/example # Destination is the absolute path where the mount will be placed in the container.
type: bind # Type specifies the mount kind.
source: /var/lib/example # Source specifies the source path of the mount.
# Options are fstab style mount options.
options:
- bind
- rshared
- rw
uidmappings: []
gidmappings: []
{{< /highlight >}}</details> | |
|`extraConfig` |Unstructured |<details><summary>The `extraConfig` field is used to provide kubelet configuration overrides.</summary><br />Some fields are not allowed to be overridden: authentication and authorization, cgroups<br />configuration, ports, etc.</details> <details><summary>Show example(s)</summary>{{< highlight yaml >}}
extraConfig:

View File

@ -1353,7 +1353,59 @@
"type": "object"
},
"ExtraMount": {
"properties": {},
"properties": {
"destination": {
"type": "string",
"title": "destination",
"description": "Destination is the absolute path where the mount will be placed in the container.\n",
"markdownDescription": "Destination is the absolute path where the mount will be placed in the container.",
"x-intellij-html-description": "\u003cp\u003eDestination is the absolute path where the mount will be placed in the container.\u003c/p\u003e\n"
},
"type": {
"type": "string",
"title": "type",
"description": "Type specifies the mount kind.\n",
"markdownDescription": "Type specifies the mount kind.",
"x-intellij-html-description": "\u003cp\u003eType specifies the mount kind.\u003c/p\u003e\n"
},
"source": {
"type": "string",
"title": "source",
"description": "Source specifies the source path of the mount.\n",
"markdownDescription": "Source specifies the source path of the mount.",
"x-intellij-html-description": "\u003cp\u003eSource specifies the source path of the mount.\u003c/p\u003e\n"
},
"options": {
"items": {
"type": "string"
},
"type": "array",
"title": "options",
"description": "Options are fstab style mount options.\n",
"markdownDescription": "Options are fstab style mount options.",
"x-intellij-html-description": "\u003cp\u003eOptions are fstab style mount options.\u003c/p\u003e\n"
},
"uidMappings": {
"items": {
"$ref": "#/$defs/LinuxIDMapping"
},
"type": "array",
"title": "uidMappings",
"description": "UID/GID mappings used for changing file owners w/o calling chown, fs should support it.\n\nEvery mount point could have its own mapping.\n",
"markdownDescription": "UID/GID mappings used for changing file owners w/o calling chown, fs should support it.\n\nEvery mount point could have its own mapping.",
"x-intellij-html-description": "\u003cp\u003eUID/GID mappings used for changing file owners w/o calling chown, fs should support it.\u003c/p\u003e\n\n\u003cp\u003eEvery mount point could have its own mapping.\u003c/p\u003e\n"
},
"gidMappings": {
"items": {
"$ref": "#/$defs/LinuxIDMapping"
},
"type": "array",
"title": "gidMappings",
"description": "UID/GID mappings used for changing file owners w/o calling chown, fs should support it.\n\nEvery mount point could have its own mapping.\n",
"markdownDescription": "UID/GID mappings used for changing file owners w/o calling chown, fs should support it.\n\nEvery mount point could have its own mapping.",
"x-intellij-html-description": "\u003cp\u003eUID/GID mappings used for changing file owners w/o calling chown, fs should support it.\u003c/p\u003e\n\n\u003cp\u003eEvery mount point could have its own mapping.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
"type": "object"
},
@ -1771,6 +1823,33 @@
"additionalProperties": false,
"type": "object"
},
"LinuxIDMapping": {
"properties": {
"containerID": {
"type": "integer",
"title": "containerID",
"description": "ContainerID is the starting UID/GID in the container.\n",
"markdownDescription": "ContainerID is the starting UID/GID in the container.",
"x-intellij-html-description": "\u003cp\u003eContainerID is the starting UID/GID in the container.\u003c/p\u003e\n"
},
"hostID": {
"type": "integer",
"title": "hostID",
"description": "HostID is the starting UID/GID on the host to be mapped to ContainerID.\n",
"markdownDescription": "HostID is the starting UID/GID on the host to be mapped to 'ContainerID'.",
"x-intellij-html-description": "\u003cp\u003eHostID is the starting UID/GID on the host to be mapped to \u0026lsquo;ContainerID\u0026rsquo;.\u003c/p\u003e\n"
},
"size": {
"type": "integer",
"title": "size",
"description": "Size is the number of IDs to be mapped.\n",
"markdownDescription": "Size is the number of IDs to be mapped.",
"x-intellij-html-description": "\u003cp\u003eSize is the number of IDs to be mapped.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
"type": "object"
},
"LoggingConfig": {
"properties": {
"destinations": {