Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move registry mirrors into certs.d directory. #49

Merged
merged 12 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.vscode/
os-metal
vendor
hack/tools/bin/*
hack/tools/bin/*
ginkgo.report
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/gardener/gardener v1.89.4
github.com/go-logr/logr v1.4.2
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.6.0
github.com/metal-stack/gardener-extension-provider-metal v0.24.4
github.com/onsi/ginkgo/v2 v2.20.0
github.com/onsi/gomega v1.34.1
Expand Down Expand Up @@ -58,7 +59,6 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
github.com/google/uuid v1.6.0 // indirect
Expand Down
207 changes: 203 additions & 4 deletions pkg/controller/operatingsystemconfig/actuator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,76 @@ import (
"context"
_ "embed"
"fmt"
"slices"
"strings"

"github.com/gardener/gardener/extensions/pkg/controller/operatingsystemconfig"
oscommonactuator "github.com/gardener/gardener/extensions/pkg/controller/operatingsystemconfig/oscommon/actuator"
gardenv1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
extensionsv1alpha1 "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1"
"github.com/go-logr/logr"
metalextensionv1alpha1 "github.com/metal-stack/gardener-extension-provider-metal/pkg/apis/metal/v1alpha1"
"github.com/metal-stack/os-metal-extension/pkg/controller/operatingsystemconfig/generator"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
)

"github.com/metal-stack/os-metal-extension/pkg/controller/operatingsystemconfig/generator"
const (
containerdConfig = `# Generated by os-extension-metal
version = 2
imports = ["/etc/containerd/conf.d/*.toml"]
disabled_plugins = []

[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
`
)

type actuator struct {
client client.Client
client client.Client
decoder runtime.Decoder
}

// NewActuator creates a new Actuator that updates the status of the handled OperatingSystemConfig resources.
func NewActuator(mgr manager.Manager) operatingsystemconfig.Actuator {
scheme := runtime.NewScheme()
utilruntime.Must(gardenv1beta1.AddToScheme(scheme))
decoder := serializer.NewCodecFactory(scheme).UniversalDecoder()

return &actuator{
client: mgr.GetClient(),
client: mgr.GetClient(),
decoder: decoder,
}
}

func (a *actuator) Reconcile(ctx context.Context, log logr.Logger, osc *extensionsv1alpha1.OperatingSystemConfig) ([]byte, *string, []string, []string, []extensionsv1alpha1.Unit, []extensionsv1alpha1.File, error) {
imageProviderConfig := &metalextensionv1alpha1.ImageProviderConfig{}

networkIsolation := &metalextensionv1alpha1.NetworkIsolation{}
if osc.Spec.ProviderConfig != nil {
err := decodeProviderConfig(a.decoder, osc.Spec.ProviderConfig, imageProviderConfig)
if err != nil {
return nil, nil, nil, nil, nil, nil, fmt.Errorf("unable to decode providerConfig")
}
}
if imageProviderConfig.NetworkIsolation != nil {
networkIsolation = imageProviderConfig.NetworkIsolation
}

extensionFiles := getExtensionFiles(osc, networkIsolation)

osc.Spec.Files = EnsureFiles(osc.Spec.Files, extensionFiles...)

cloudConfig, command, err := oscommonactuator.CloudConfigFromOperatingSystemConfig(ctx, log, a.client, osc, generator.IgnitionGenerator())
if err != nil {
return nil, nil, nil, nil, nil, nil, fmt.Errorf("could not generate cloud config: %w", err)
}

return cloudConfig, command, oscommonactuator.OperatingSystemConfigUnitNames(osc), oscommonactuator.OperatingSystemConfigFilePaths(osc), nil, nil, nil
return cloudConfig, command, oscommonactuator.OperatingSystemConfigUnitNames(osc), oscommonactuator.OperatingSystemConfigFilePaths(osc), nil, extensionFiles, nil
}

func (a *actuator) Delete(_ context.Context, _ logr.Logger, _ *extensionsv1alpha1.OperatingSystemConfig) error {
Expand All @@ -64,3 +105,161 @@ func (a *actuator) ForceDelete(ctx context.Context, log logr.Logger, osc *extens
func (a *actuator) Restore(ctx context.Context, log logr.Logger, osc *extensionsv1alpha1.OperatingSystemConfig) ([]byte, *string, []string, []string, []extensionsv1alpha1.Unit, []extensionsv1alpha1.File, error) {
return a.Reconcile(ctx, log, osc)
}

func getExtensionFiles(osc *extensionsv1alpha1.OperatingSystemConfig, networkIsolation *metalextensionv1alpha1.NetworkIsolation) []extensionsv1alpha1.File {
var extensionFiles []extensionsv1alpha1.File

if len(networkIsolation.RegistryMirrors) > 0 {
dnsFiles := additionalDNSConfFiles(networkIsolation.DNSServers)
extensionFiles = append(extensionFiles, dnsFiles...)

ntpFiles := additionalNTPConfFiles(networkIsolation.NTPServers)
extensionFiles = append(extensionFiles, ntpFiles...)
}

if osc.Spec.CRIConfig != nil && osc.Spec.CRIConfig.Name == extensionsv1alpha1.CRINameContainerD {
// the debian:12 containerd ships with "cri" plugin disabled, so we need override the config that ships with the os
//
// with g/g v1.100 it would be best to just remove the config.toml and let the GNA generate the default config.
// unfortunately, ignition does not allow to remove files easily.
// along with the default import paths. see https://github.com/gardener/gardener/pull/10050)
extensionFiles = append(extensionFiles, extensionsv1alpha1.File{
Path: "/etc/containerd/config.toml",
Permissions: ptr.To(int32(0644)),
Content: extensionsv1alpha1.FileContent{
Inline: &extensionsv1alpha1.FileContentInline{
Encoding: string(extensionsv1alpha1.PlainFileCodecID),
Data: containerdConfig,
},
},
})

if len(networkIsolation.RegistryMirrors) > 0 {
extensionFiles = append(extensionFiles, additionalContainerdMirrors(networkIsolation.RegistryMirrors)...)
}
}

return extensionFiles
}

// decodeProviderConfig decodes the provider config into the given struct
func decodeProviderConfig(decoder runtime.Decoder, providerConfig *runtime.RawExtension, into runtime.Object) error {
if providerConfig == nil {
return nil
}

if _, _, err := decoder.Decode(providerConfig.Raw, nil, into); err != nil {
return fmt.Errorf("could not decode provider config: %w", err)
}

return nil
}

func additionalContainerdMirrors(mirrors []metalextensionv1alpha1.RegistryMirror) []extensionsv1alpha1.File {
var files []extensionsv1alpha1.File

for _, m := range mirrors {
for _, of := range m.MirrorOf {
content := fmt.Sprintf(`server = "https://%s"

[host.%q]
capabilities = ["pull", "resolve"]
`, of, m.Endpoint)

files = append(files, extensionsv1alpha1.File{
Path: fmt.Sprintf("/etc/containerd/certs.d/%s/hosts.toml", of),
Content: extensionsv1alpha1.FileContent{
Inline: &extensionsv1alpha1.FileContentInline{
Encoding: string(extensionsv1alpha1.PlainFileCodecID),
Data: content,
},
},
})
}
}

return files
}

func additionalDNSConfFiles(dnsServers []string) []extensionsv1alpha1.File {
if len(dnsServers) == 0 {
return nil
}
resolveDNS := strings.Join(dnsServers, " ")
systemdResolvedConfd := fmt.Sprintf(`# Generated by os-extension-metal
[Resolve]
DNS=%s
Domain=~.
`, resolveDNS)
resolvConf := "# Generated by os-extension-metal\n"
for _, ip := range dnsServers {
resolvConf += fmt.Sprintf("nameserver %s\n", ip)
}

// TODO: in osc.Spec.Type we can get the distro "ubuntu", "debian", "nvidia", ...
// from this information we should be able to deduce if systemd-resolved is used or not

return []extensionsv1alpha1.File{
{
Path: "/etc/systemd/resolved.conf.d/dns.conf",
Content: extensionsv1alpha1.FileContent{
Inline: &extensionsv1alpha1.FileContentInline{
Encoding: string(extensionsv1alpha1.PlainFileCodecID),
Data: systemdResolvedConfd,
},
},
},
{
Path: "/etc/resolv.conf",
Content: extensionsv1alpha1.FileContent{
Inline: &extensionsv1alpha1.FileContentInline{
Encoding: string(extensionsv1alpha1.PlainFileCodecID),
Data: resolvConf,
},
},
},
}
}

func additionalNTPConfFiles(ntpServers []string) []extensionsv1alpha1.File {
if len(ntpServers) == 0 {
return nil
}
ntps := strings.Join(ntpServers, " ")
renderedContent := fmt.Sprintf(`# Generated by os-extension-metal
[Time]
NTP=%s
`, ntps)

return []extensionsv1alpha1.File{
{
Path: "/etc/systemd/timesyncd.conf",
Content: extensionsv1alpha1.FileContent{
Inline: &extensionsv1alpha1.FileContentInline{
Encoding: string(extensionsv1alpha1.PlainFileCodecID),
Data: renderedContent,
},
},
},
}
}

func EnsureFiles(base []extensionsv1alpha1.File, files ...extensionsv1alpha1.File) []extensionsv1alpha1.File {
var res []extensionsv1alpha1.File

res = append(res, base...)

for _, file := range files {
index := slices.IndexFunc(base, func(elem extensionsv1alpha1.File) bool {
return elem.Path == file.Path
})

if index < 0 {
res = append(res, file)
} else {
res[index] = file
}
}

return res
}
Loading