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 }