diff --git a/.yamllint b/.yamllint index a7c8c3f9e00..dae683c8f61 100644 --- a/.yamllint +++ b/.yamllint @@ -2,6 +2,10 @@ extends: default +ignore: | + # this is a yaml template, needs to be executed + pkg/cidata/cloud-config.yaml + rules: indentation: indent-sequences: false diff --git a/cmd/limactl/factory-reset.go b/cmd/limactl/factory-reset.go index 9e98f501639..c13a450fd2c 100644 --- a/cmd/limactl/factory-reset.go +++ b/cmd/limactl/factory-reset.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/lima-vm/lima/pkg/cidata" "github.com/lima-vm/lima/pkg/instance" "github.com/lima-vm/lima/pkg/store" "github.com/lima-vm/lima/pkg/store/filenames" @@ -63,6 +64,11 @@ func factoryResetAction(_ *cobra.Command, args []string) error { } } } + // Regenerate the cloud-config.yaml, to reflect any changes to the global _config + if err := cidata.GenerateCloudConfig(inst.Dir, instName, inst.Config); err != nil { + logrus.Error(err) + } + logrus.Infof("Instance %q has been factory reset", instName) return nil } diff --git a/pkg/cidata/cidata.TEMPLATE.d/user-data b/pkg/cidata/cidata.TEMPLATE.d/user-data index 5bbd994030c..d98d03591d8 100644 --- a/pkg/cidata/cidata.TEMPLATE.d/user-data +++ b/pkg/cidata/cidata.TEMPLATE.d/user-data @@ -39,6 +39,7 @@ users: - {{ printf "%q" $val }} {{- end }} +{{- if .BootScripts }} write_files: - content: | #!/bin/sh @@ -52,6 +53,7 @@ write_files: owner: root:root path: /var/lib/cloud/scripts/per-boot/00-lima.boot.sh permissions: '0755' +{{- end }} {{- if .DNSAddresses }} # This has no effect on systems using systemd-resolved, but is used @@ -66,9 +68,12 @@ resolv_conf: {{- end }} {{- end }} +{{- if or .CACerts.RemoveDefaults .CACerts.Trusted }} {{ with .CACerts }} ca_certs: + {{- if .RemoveDefaults }} remove_defaults: {{ .RemoveDefaults }} + {{- end }} {{- if .Trusted}} trusted: {{- range $cert := .Trusted }} @@ -76,6 +81,7 @@ ca_certs: {{- range $line := $cert.Lines }} {{ $line }} {{- end }} + {{- end }} {{- end }} {{- end }} {{- end }} diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go index 80d8d6ad953..722c1a6c947 100644 --- a/pkg/cidata/cidata.go +++ b/pkg/cidata/cidata.go @@ -111,19 +111,20 @@ func setupEnv(instConfigEnv map[string]string, propagateProxyEnv bool, slirpGate return env, nil } -func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, nerdctlArchive string, vsockPort int, virtioPort string) error { +func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort, vsockPort int, virtioPort string) (*TemplateArgs, error) { if err := limayaml.Validate(instConfig, false); err != nil { - return err + return nil, err } u, err := osutil.LimaUser(true) if err != nil { - return err + return nil, err } uid, err := strconv.Atoi(u.Uid) if err != nil { - return err + return nil, err } args := TemplateArgs{ + BootScripts: bootScripts, Name: name, User: u.Username, UID: uid, @@ -150,14 +151,14 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS usernetName := instConfig.Networks[firstUsernetIndex].Lima subnet, err = usernet.Subnet(usernetName) if err != nil { - return err + return nil, err } args.SlirpGateway = usernet.GatewayIP(subnet) args.SlirpDNS = usernet.GatewayIP(subnet) } else { subnet, _, err = net.ParseCIDR(networks.SlirpNetwork) if err != nil { - return err + return nil, err } args.SlirpGateway = usernet.GatewayIP(subnet) if *instConfig.VMType == limayaml.VZ { @@ -173,10 +174,10 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS pubKeys, err := sshutil.DefaultPubKeys(*instConfig.SSH.LoadDotSSHPubKeys) if err != nil { - return err + return nil, err } if len(pubKeys) == 0 { - return errors.New("no SSH key was found, run `ssh-keygen`") + return nil, errors.New("no SSH key was found, run `ssh-keygen`") } for _, f := range pubKeys { args.SSHPubKeys = append(args.SSHPubKeys, f.Content) @@ -193,17 +194,17 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS } hostHome, err := localpathutil.Expand("~") if err != nil { - return err + return nil, err } for i, f := range instConfig.Mounts { tag := fmt.Sprintf("mount%d", i) location, err := localpathutil.Expand(f.Location) if err != nil { - return err + return nil, err } mountPoint, err := localpathutil.Expand(f.MountPoint) if err != nil { - return err + return nil, err } options := "defaults" switch fstype { @@ -217,7 +218,7 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS options += fmt.Sprintf(",version=%s", *f.NineP.ProtocolVersion) msize, err := units.RAMInBytes(*f.NineP.Msize) if err != nil { - return fmt.Errorf("failed to parse msize for %q: %w", location, err) + return nil, fmt.Errorf("failed to parse msize for %q: %w", location, err) } options += fmt.Sprintf(",msize=%d", msize) options += fmt.Sprintf(",cache=%s", *f.NineP.Cache) @@ -268,7 +269,7 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS args.Env, err = setupEnv(instConfig.Env, *instConfig.PropagateProxyEnv, args.SlirpGateway) if err != nil { - return err + return nil, err } switch { @@ -285,7 +286,7 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS default: args.DNSAddresses, err = osutil.DNSAddresses() if err != nil { - return err + return nil, err } } @@ -294,12 +295,12 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS for _, path := range instConfig.CACertificates.Files { expanded, err := localpathutil.Expand(path) if err != nil { - return err + return nil, err } content, err := os.ReadFile(expanded) if err != nil { - return err + return nil, err } cert := getCert(string(content)) @@ -311,6 +312,12 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS args.CACerts.Trusted = append(args.CACerts.Trusted, cert) } + // Remove empty caCerts (default values) from configuration yaml + if !*args.CACerts.RemoveDefaults && len(args.CACerts.Trusted) == 0 { + args.CACerts.RemoveDefaults = nil + args.CACerts.Trusted = nil + } + args.BootCmds = getBootCmds(instConfig.Provision) for _, f := range instConfig.Provision { @@ -319,11 +326,43 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS } } + return &args, nil +} + +func GenerateCloudConfig(instDir, name string, instConfig *limayaml.LimaYAML) error { + args, err := templateArgs(false, instDir, name, instConfig, 0, 0, 0, "") + if err != nil { + return err + } + // mounts are not included here + args.Mounts = nil + // resolv_conf is not included here + args.DNSAddresses = nil + + if err := ValidateTemplateArgs(args); err != nil { + return err + } + + config, err := ExecuteTemplateCloudConfig(args) + if err != nil { + return err + } + + os.RemoveAll(filepath.Join(instDir, filenames.CloudConfig)) // delete existing + return os.WriteFile(filepath.Join(instDir, filenames.CloudConfig), config, 0o444) +} + +func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, nerdctlArchive string, vsockPort int, virtioPort string) error { + args, err := templateArgs(true, instDir, name, instConfig, udpDNSLocalPort, tcpDNSLocalPort, vsockPort, virtioPort) + if err != nil { + return err + } + if err := ValidateTemplateArgs(args); err != nil { return err } - layout, err := ExecuteTemplate(args) + layout, err := ExecuteTemplateCIDataISO(args) if err != nil { return err } diff --git a/pkg/cidata/cloud-config.yaml b/pkg/cidata/cloud-config.yaml new file mode 120000 index 00000000000..4bec8830e5f --- /dev/null +++ b/pkg/cidata/cloud-config.yaml @@ -0,0 +1 @@ +cidata.TEMPLATE.d/user-data \ No newline at end of file diff --git a/pkg/cidata/template.go b/pkg/cidata/template.go index 2a18ba43862..60abc16a961 100644 --- a/pkg/cidata/template.go +++ b/pkg/cidata/template.go @@ -75,6 +75,7 @@ type TemplateArgs struct { TCPDNSLocalPort int Env map[string]string Param map[string]string + BootScripts bool DNSAddresses []string CACerts CACerts HostHomeMountPoint string @@ -89,7 +90,7 @@ type TemplateArgs struct { TimeZone string } -func ValidateTemplateArgs(args TemplateArgs) error { +func ValidateTemplateArgs(args *TemplateArgs) error { if err := identifiers.Validate(args.Name); err != nil { return err } @@ -114,13 +115,24 @@ func ValidateTemplateArgs(args TemplateArgs) error { return fmt.Errorf("field mounts[%d] must be absolute, got %q", i, f) } } - if args.CACerts.RemoveDefaults == nil { - return errors.New("field CACerts.RemoveDefaults must be set") - } return nil } -func ExecuteTemplate(args TemplateArgs) ([]iso9660util.Entry, error) { +func ExecuteTemplateCloudConfig(args *TemplateArgs) ([]byte, error) { + if err := ValidateTemplateArgs(args); err != nil { + return nil, err + } + + userData, err := templateFS.ReadFile(path.Join(templateFSRoot, "user-data")) + if err != nil { + return nil, err + } + + cloudConfigYaml := string(userData) + return textutil.ExecuteTemplate(cloudConfigYaml, args) +} + +func ExecuteTemplateCIDataISO(args *TemplateArgs) ([]iso9660util.Entry, error) { if err := ValidateTemplateArgs(args); err != nil { return nil, err } diff --git a/pkg/cidata/template_test.go b/pkg/cidata/template_test.go index a8ae1cc895d..35c3623e991 100644 --- a/pkg/cidata/template_test.go +++ b/pkg/cidata/template_test.go @@ -10,8 +10,45 @@ import ( var defaultRemoveDefaults = false +func TestConfig(t *testing.T) { + args := &TemplateArgs{ + Name: "default", + User: "foo", + UID: 501, + Home: "/home/foo.linux", + SSHPubKeys: []string{ + "ssh-rsa dummy foo@example.com", + }, + MountType: "reverse-sshfs", + } + config, err := ExecuteTemplateCloudConfig(args) + assert.NilError(t, err) + t.Log(string(config)) + assert.Assert(t, !strings.Contains(string(config), "ca_certs:")) +} + +func TestConfigCACerts(t *testing.T) { + args := &TemplateArgs{ + Name: "default", + User: "foo", + UID: 501, + Home: "/home/foo.linux", + SSHPubKeys: []string{ + "ssh-rsa dummy foo@example.com", + }, + MountType: "reverse-sshfs", + CACerts: CACerts{ + RemoveDefaults: &defaultRemoveDefaults, + }, + } + config, err := ExecuteTemplateCloudConfig(args) + assert.NilError(t, err) + t.Log(string(config)) + assert.Assert(t, strings.Contains(string(config), "ca_certs:")) +} + func TestTemplate(t *testing.T) { - args := TemplateArgs{ + args := &TemplateArgs{ Name: "default", User: "foo", UID: 501, @@ -29,7 +66,7 @@ func TestTemplate(t *testing.T) { Trusted: []Cert{}, }, } - layout, err := ExecuteTemplate(args) + layout, err := ExecuteTemplateCIDataISO(args) assert.NilError(t, err) for _, f := range layout { t.Logf("=== %q ===", f.Path) @@ -46,7 +83,7 @@ func TestTemplate(t *testing.T) { } func TestTemplate9p(t *testing.T) { - args := TemplateArgs{ + args := &TemplateArgs{ Name: "default", User: "foo", UID: 501, @@ -63,7 +100,7 @@ func TestTemplate9p(t *testing.T) { RemoveDefaults: &defaultRemoveDefaults, }, } - layout, err := ExecuteTemplate(args) + layout, err := ExecuteTemplateCIDataISO(args) assert.NilError(t, err) for _, f := range layout { t.Logf("=== %q ===", f.Path) diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go index 1125cfb9dbc..0c260b13e64 100644 --- a/pkg/hostagent/hostagent.go +++ b/pkg/hostagent/hostagent.go @@ -133,6 +133,9 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt virtioPort = "" // filenames.VirtioPort } + if err := cidata.GenerateCloudConfig(inst.Dir, instName, inst.Config); err != nil { + return nil, err + } if err := cidata.GenerateISO9660(inst.Dir, instName, inst.Config, udpDNSLocalPort, tcpDNSLocalPort, o.nerdctlArchive, vSockPort, virtioPort); err != nil { return nil, err } diff --git a/pkg/instance/create.go b/pkg/instance/create.go index f8678cb45cc..6a3f91d1c01 100644 --- a/pkg/instance/create.go +++ b/pkg/instance/create.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" + "github.com/lima-vm/lima/pkg/cidata" "github.com/lima-vm/lima/pkg/driver" "github.com/lima-vm/lima/pkg/driverutil" "github.com/lima-vm/lima/pkg/limayaml" @@ -60,6 +61,9 @@ func Create(ctx context.Context, instName string, instConfig []byte, saveBrokenY if err := os.WriteFile(filePath, instConfig, 0o644); err != nil { return nil, err } + if err := cidata.GenerateCloudConfig(instDir, instName, loadedInstConfig); err != nil { + return nil, err + } if err := os.WriteFile(filepath.Join(instDir, filenames.LimaVersion), []byte(version.Version), 0o444); err != nil { return nil, err } diff --git a/pkg/store/filenames/filenames.go b/pkg/store/filenames/filenames.go index f07300a6674..18e589b0753 100644 --- a/pkg/store/filenames/filenames.go +++ b/pkg/store/filenames/filenames.go @@ -30,6 +30,7 @@ const ( LimaVersion = "lima-version" // Lima version used to create instance CIDataISO = "cidata.iso" CIDataISODir = "cidata" + CloudConfig = "cloud-config.yaml" BaseDisk = "basedisk" DiffDisk = "diffdisk" Kernel = "kernel" diff --git a/website/content/en/docs/dev/internals/_index.md b/website/content/en/docs/dev/internals/_index.md index 03d94eaf03f..b71ce546ab7 100644 --- a/website/content/en/docs/dev/internals/_index.md +++ b/website/content/en/docs/dev/internals/_index.md @@ -37,6 +37,7 @@ Metadata: - `protected`: empty file, used by `limactl protect` cloud-init: +- `cloud-config.yaml`: cloud-init configuration, for reference only. - `cidata.iso`: cloud-init ISO9660 image. See [`cidata.iso`](#cidataiso). Ansible: