github.com/coreos/mantle@v0.13.0/cmd/ore/do/create-image.go (about)

     1  // Copyright 2017 CoreOS, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package do
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os"
    21  	"time"
    22  
    23  	"github.com/pborman/uuid"
    24  	"github.com/spf13/cobra"
    25  
    26  	ctplatform "github.com/coreos/container-linux-config-transpiler/config/platform"
    27  	"github.com/coreos/mantle/platform"
    28  	"github.com/coreos/mantle/platform/conf"
    29  	"github.com/coreos/mantle/util"
    30  )
    31  
    32  var (
    33  	cmdCreateImage = &cobra.Command{
    34  		Use:   "create-image [options]",
    35  		Short: "Create image",
    36  		Long:  `Create an image.`,
    37  		RunE:  runCreateImage,
    38  	}
    39  )
    40  
    41  func init() {
    42  	DO.AddCommand(cmdCreateImage)
    43  	cmdCreateImage.Flags().StringVar(&options.Region, "region", "sfo2", "region slug")
    44  	cmdCreateImage.Flags().StringVarP(&imageName, "name", "n", "", "image name")
    45  	cmdCreateImage.Flags().StringVarP(&imageURL, "url", "u", "", "image source URL (e.g. \"https://stable.release.core-os.net/amd64-usr/current/coreos_production_digitalocean_image.bin.bz2\"")
    46  }
    47  
    48  func runCreateImage(cmd *cobra.Command, args []string) error {
    49  	if len(args) != 0 {
    50  		fmt.Fprintf(os.Stderr, "Unrecognized args in do create-image cmd: %v\n", args)
    51  		os.Exit(2)
    52  	}
    53  
    54  	if err := createImage(); err != nil {
    55  		fmt.Fprintf(os.Stderr, "%v\n", err)
    56  		os.Exit(1)
    57  	}
    58  
    59  	return nil
    60  }
    61  
    62  func createImage() error {
    63  	if imageName == "" {
    64  		return fmt.Errorf("Image name must be specified")
    65  	}
    66  	if imageURL == "" {
    67  		return fmt.Errorf("Image URL must be specified")
    68  	}
    69  
    70  	// set smallest available size, so the image will run on any size droplet
    71  	options.Size = "512mb"
    72  
    73  	userdata, err := makeUserData()
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	ctx := context.Background()
    79  
    80  	key, err := platform.GenerateFakeKey()
    81  	if err != nil {
    82  		return err
    83  	}
    84  	keyID, err := API.AddKey(ctx, "ore-"+uuid.New(), key)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	defer API.DeleteKey(ctx, keyID)
    89  
    90  	droplet, err := API.CreateDroplet(ctx, imageName+"-install", keyID, userdata)
    91  	if err != nil {
    92  		return fmt.Errorf("couldn't create droplet: %v", err)
    93  	}
    94  	dropletID := droplet.ID
    95  	defer API.DeleteDroplet(ctx, dropletID)
    96  
    97  	// the droplet will power itself off when install completes
    98  	err = util.WaitUntilReady(10*time.Minute, 15*time.Second, func() (bool, error) {
    99  		droplet, err := API.GetDroplet(ctx, dropletID)
   100  		if err != nil {
   101  			return false, err
   102  		}
   103  		return droplet.Status == "off", nil
   104  	})
   105  	if err != nil {
   106  		return fmt.Errorf("Failed waiting for droplet to power off (%v). Did install fail?", err)
   107  	}
   108  
   109  	if err := API.SnapshotDroplet(ctx, dropletID, imageName); err != nil {
   110  		return fmt.Errorf("couldn't snapshot droplet: %v", err)
   111  	}
   112  
   113  	return nil
   114  }
   115  
   116  func makeUserData() (string, error) {
   117  	clc := fmt.Sprintf(`storage:
   118    files:
   119      - filesystem: root
   120        path: /root/initramfs/etc/resolv.conf
   121        mode: 0644
   122        contents:
   123          inline: nameserver 8.8.8.8
   124      - filesystem: root
   125        path: /root/initramfs/shutdown
   126        mode: 0755
   127        contents:
   128          inline: |
   129            #!/busybox sh
   130  
   131            set -e -o pipefail
   132  
   133            echo "Starting install..."
   134            disk=$(/busybox mountpoint -n /oldroot | /busybox sed -e 's/p*[0-9]* .*//')
   135  
   136            echo "Unmounting filesystems..."
   137            /busybox find /oldroot -depth -type d -exec /busybox mountpoint -q {} \; -exec /busybox umount {} \;
   138            # Verify success
   139            /busybox mountpoint -q /oldroot && /busybox false
   140  
   141            echo "Zeroing ${disk}..."
   142            /busybox dd if=/dev/zero of="${disk}" bs=1M ||:
   143  
   144            echo "Installing to ${disk}..."
   145            /busybox wget -O - "%s" | \
   146                /busybox bunzip2 -c | \
   147                /busybox dd of="${disk}" bs=1M
   148  
   149            echo "Shutting down..."
   150            /busybox poweroff -f
   151  systemd:
   152    units:
   153      - name: install-prep.service
   154        enabled: true
   155        contents: |
   156          [Unit]
   157          Description=Launch Install
   158          After=multi-user.target
   159  
   160          [Service]
   161          Type=oneshot
   162  
   163          # https://github.com/coreos/bugs/issues/2205
   164          ExecStart=/usr/bin/wget -O /root/initramfs/busybox https://busybox.net/downloads/binaries/1.27.1-i686/busybox
   165          ExecStart=/bin/sh -c 'echo "b51b9328eb4e60748912e1c1867954a5cf7e9d5294781cae59ce225ed110523c /root/initramfs/busybox" | sha256sum -c -'
   166          ExecStart=/usr/bin/chmod +x /root/initramfs/busybox
   167  
   168          ExecStart=/usr/bin/rsync -a /root/initramfs/ /run/initramfs
   169          ExecStart=/usr/bin/systemctl --no-block poweroff
   170  
   171          [Install]
   172          WantedBy=multi-user.target
   173  `, imageURL)
   174  
   175  	conf, err := conf.ContainerLinuxConfig(clc).Render(ctplatform.DO)
   176  	if err != nil {
   177  		return "", fmt.Errorf("Couldn't render userdata: %v", err)
   178  	}
   179  	return conf.String(), nil
   180  }