github.com/coreos/mantle@v0.13.0/cmd/ore/gcloud/upload.go (about) 1 // Copyright 2015 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 gcloud 16 17 import ( 18 "fmt" 19 "net/url" 20 "os" 21 "path/filepath" 22 "strings" 23 24 "github.com/spf13/cobra" 25 "google.golang.org/api/googleapi" 26 "google.golang.org/api/storage/v1" 27 28 "github.com/coreos/mantle/platform/api/gcloud" 29 "github.com/coreos/mantle/sdk" 30 ) 31 32 var ( 33 cmdUpload = &cobra.Command{ 34 Use: "upload", 35 Short: "Upload os image", 36 Long: "Upload os image to Google Storage bucket and create image in GCE. Intended for use in SDK.", 37 Run: runUpload, 38 } 39 40 uploadBucket string 41 uploadImageName string 42 uploadBoard string 43 uploadFile string 44 uploadForce bool 45 ) 46 47 func init() { 48 build := sdk.BuildRoot() 49 cmdUpload.Flags().StringVar(&uploadBucket, "bucket", "gs://users.developer.core-os.net", "gs://bucket/prefix/ prefix defaults to $USER") 50 cmdUpload.Flags().StringVar(&uploadImageName, "name", "", "name for uploaded image, defaults to COREOS_VERSION") 51 cmdUpload.Flags().StringVar(&uploadBoard, "board", "amd64-usr", "board used for naming with default prefix only") 52 cmdUpload.Flags().StringVar(&uploadFile, "file", 53 build+"/images/amd64-usr/latest/coreos_production_gce.tar.gz", 54 "path_to_coreos_image (build with: ./image_to_vm.sh --format=gce ...)") 55 cmdUpload.Flags().BoolVar(&uploadForce, "force", false, "overwrite existing GS and GCE images without prompt") 56 GCloud.AddCommand(cmdUpload) 57 } 58 59 func runUpload(cmd *cobra.Command, args []string) { 60 if len(args) != 0 { 61 fmt.Fprintf(os.Stderr, "Unrecognized args in plume upload cmd: %v\n", args) 62 os.Exit(2) 63 } 64 65 // if an image name is unspecified try to use version.txt 66 if uploadImageName == "" { 67 ver, err := sdk.VersionsFromDir(filepath.Dir(uploadFile)) 68 if err != nil { 69 fmt.Fprintf(os.Stderr, "Unable to get version from image directory, provide a -name flag or include a version.txt in the image directory: %v\n", err) 70 os.Exit(1) 71 } 72 uploadImageName = ver.Version 73 } 74 75 gsURL, err := url.Parse(uploadBucket) 76 if err != nil { 77 fmt.Fprintf(os.Stderr, "%v\n", err) 78 os.Exit(1) 79 } 80 if gsURL.Scheme != "gs" { 81 fmt.Fprintf(os.Stderr, "URL missing gs:// scheme prefix: %v\n", uploadBucket) 82 os.Exit(1) 83 } 84 if gsURL.Host == "" { 85 fmt.Fprintf(os.Stderr, "URL missing bucket name %v\n", uploadBucket) 86 os.Exit(1) 87 } 88 // if prefix not specified default name to gs://bucket/$USER/$BOARD/$VERSION 89 if gsURL.Path == "" { 90 if user := os.Getenv("USER"); user != "" { 91 gsURL.Path = "/" + os.Getenv("USER") 92 gsURL.Path += "/" + uploadBoard 93 } 94 } 95 96 uploadBucket = gsURL.Host 97 uploadImageName = strings.TrimPrefix(gsURL.Path+"/"+uploadImageName, "/") 98 // create equivalent image names for GS and GCE 99 imageNameGCE := gceSanitize(uploadImageName) 100 imageNameGS := uploadImageName + ".tar.gz" 101 102 storageAPI, err := storage.New(api.Client()) 103 if err != nil { 104 fmt.Fprintf(os.Stderr, "Storage client failed: %v\n", err) 105 os.Exit(1) 106 } 107 108 // check if this file is already uploaded and give option to skip 109 alreadyExists, err := fileQuery(storageAPI, uploadBucket, imageNameGS) 110 if err != nil { 111 fmt.Fprintf(os.Stderr, "Uploading image failed: %v\n", err) 112 os.Exit(1) 113 } 114 115 if alreadyExists && !uploadForce { 116 var ans string 117 fmt.Printf("File %v already exists on Google Storage. Overwrite? (y/n):", imageNameGS) 118 if _, err = fmt.Scan(&ans); err != nil { 119 fmt.Fprintf(os.Stderr, "Scanning overwrite input: %v", err) 120 os.Exit(1) 121 } 122 switch ans { 123 case "y", "Y", "yes": 124 fmt.Println("Overriding existing file...") 125 err = writeFile(storageAPI, uploadBucket, uploadFile, imageNameGS) 126 default: 127 fmt.Println("Skipped file upload") 128 } 129 } else { 130 err = writeFile(storageAPI, uploadBucket, uploadFile, imageNameGS) 131 } 132 if err != nil { 133 fmt.Fprintf(os.Stderr, "Uploading image failed: %v\n", err) 134 os.Exit(1) 135 } 136 137 fmt.Printf("Creating image in GCE: %v...\n", imageNameGCE) 138 139 // create image on gce 140 storageSrc := fmt.Sprintf("https://storage.googleapis.com/%v/%v", uploadBucket, imageNameGS) 141 _, pending, err := api.CreateImage(&gcloud.ImageSpec{ 142 Name: imageNameGCE, 143 SourceImage: storageSrc, 144 }, uploadForce) 145 if err == nil { 146 err = pending.Wait() 147 } 148 149 // if image already exists ask to delete and try again 150 if err != nil && strings.HasSuffix(err.Error(), "alreadyExists") { 151 var ans string 152 fmt.Printf("Image %v already exists on GCE. Overwrite? (y/n):", imageNameGCE) 153 if _, err = fmt.Scan(&ans); err != nil { 154 fmt.Fprintf(os.Stderr, "Scanning overwrite input: %v", err) 155 os.Exit(1) 156 } 157 switch ans { 158 case "y", "Y", "yes": 159 fmt.Println("Overriding existing image...") 160 _, pending, err = api.CreateImage(&gcloud.ImageSpec{ 161 Name: imageNameGCE, 162 SourceImage: storageSrc, 163 }, true) 164 if err == nil { 165 err = pending.Wait() 166 } 167 if err != nil { 168 fmt.Fprintf(os.Stderr, "Creating GCE image failed: %v\n", err) 169 os.Exit(1) 170 } 171 fmt.Printf("Image %v sucessfully created in GCE\n", imageNameGCE) 172 default: 173 fmt.Println("Skipped GCE image creation") 174 } 175 } 176 177 if err != nil { 178 fmt.Fprintf(os.Stderr, "Creating GCE image failed: %v\n", err) 179 os.Exit(1) 180 } 181 } 182 183 // Converts an image name from Google Storage to an equivalent GCE image 184 // name. NOTE: Not a fully generlized sanitizer for GCE. Designed for 185 // the default version.txt name (ex: 633.1.0+2015-03-31-1538). See: 186 // https://godoc.org/google.golang.org/api/compute/v1#Image 187 func gceSanitize(name string) string { 188 if name == "" { 189 return name 190 } 191 192 // remove incompatible chars from version.txt 193 name = strings.Replace(name, ".", "-", -1) 194 name = strings.Replace(name, "+", "-", -1) 195 196 // remove forward slashes likely from prefix 197 name = strings.Replace(name, "/", "-", -1) 198 199 // ensure name starts with [a-z] 200 char := name[0] 201 if char >= 'a' && char <= 'z' { 202 return name 203 } 204 if char >= 'A' && char <= 'Z' { 205 return strings.ToLower(name[:1]) + name[1:] 206 } 207 return "v" + name 208 } 209 210 // Write file to Google Storage 211 func writeFile(api *storage.Service, bucket, filename, destname string) error { 212 fmt.Printf("Writing %v to gs://%v ...\n", filename, bucket) 213 fmt.Printf("(Sometimes this takes a few minutes)\n") 214 215 file, err := os.Open(filename) 216 if err != nil { 217 return err 218 } 219 defer file.Close() 220 221 req := api.Objects.Insert(bucket, &storage.Object{ 222 Name: destname, 223 ContentType: "application/x-gzip", 224 }) 225 req.PredefinedAcl("authenticatedRead") 226 req.Media(file) 227 228 if _, err := req.Do(); err != nil { 229 return err 230 } 231 232 fmt.Printf("Upload successful!\n") 233 return nil 234 } 235 236 // Test if file exists in Google Storage 237 func fileQuery(api *storage.Service, bucket, name string) (bool, error) { 238 req := api.Objects.Get(bucket, name) 239 if _, err := req.Do(); err != nil { 240 if e, ok := err.(*googleapi.Error); ok && e.Code == 404 { 241 return false, nil 242 } 243 return false, err 244 } 245 return true, nil 246 }