Search code examples
cloud-initmultipass

Issues running commands via cloud-init


An open source project I'm working on has components that require Linux and consequently virtualization has generally been the best solution for development and testing new features. I'm attempting to provide a simple cloud-init file for Multipass that will configure the VM with our code by pulling our files from Git and setting them up in the VM automatically. However, even though extra time elapsed for launch seems to indicate the process is being run, no files seem to actually be saved to the home directory, even for simpler cases, i.e.

runcmd:
  - [ cd, ~ ]
  - [ touch test ]
  - [ echo 'test' > test ]

Am I just misconfiguring cloud-init or am I missing something crucial?


Solution

  • There are a couple of problems going on here.

    First, your cloud config user data must begin with the line:

    #cloud-config
    

    Without that line, cloud-init doesn't know what to do with it. If you were to submit a user-data configuration like this:

    #cloud-config
    runcmd:
      - [ cd, ~ ]
      - [ touch test ]
      - [ echo 'test' > test ]
    

    You would find the following errors in /var/log/cloud-init-output.log:

    runcmd.0: ['cd', None] is not valid under any of the given schemas
    /var/lib/cloud/instance/scripts/runcmd: 2: cd: can't cd to None
    /var/lib/cloud/instance/scripts/runcmd: 3: touch test: not found
    /var/lib/cloud/instance/scripts/runcmd: 4: echo 'test' > test: not found
    

    You'll find the solution to these problems in the documentation, which includes this note about runcmd:

    # run commands
    # default: none
    # runcmd contains a list of either lists or a string
    # each item will be executed in order at rc.local like level with
    # output to the console
    # - runcmd only runs during the first boot
    # - if the item is a list, the items will be properly executed as if
    #   passed to execve(3) (with the first arg as the command).
    # - if the item is a string, it will be simply written to the file and
    #   will be interpreted by 'sh'
    

    You passed a list of lists, so the behavior is governed by "*if the item is a list, the items will be properly executed as if passed to execve(3) (with the first arg as the command)". In this case, the ~ in [cd, ~] doesn't make any sense -- the command isn't being executed by the shell, so there's nothing to expand ~.

    The second two commands include on a single list item, and there is no command on your system named either touch test or echo 'test' > test.

    The simplest solution here is to simply pass in a list of strings intead:

    #cloud-config
    runcmd:
      - cd /root
      - touch test
      - echo 'test' > test
    

    I've replaced cd ~ here with cd /root, because it seems better to be explicit (and you know these commands are running as root anyway).