github.com/mfpierre/corectl@v0.5.6/pull.go (about)

     1  // Copyright 2015 - António Meireles  <antonio.meireles@reformi.st>
     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  
    16  package main
    17  
    18  import (
    19  	"bufio"
    20  	"bytes"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"log"
    24  	"net/http"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  
    29  	"github.com/TheNewNormal/corectl/image"
    30  
    31  	"github.com/blang/semver"
    32  	"github.com/spf13/cobra"
    33  )
    34  
    35  var (
    36  	pullCmd = &cobra.Command{
    37  		Use:     "pull",
    38  		Aliases: []string{"get", "fetch"},
    39  		Short:   "Pulls a CoreOS image from upstream",
    40  		PreRunE: defaultPreRunE,
    41  		RunE:    pullCommand,
    42  	}
    43  )
    44  
    45  func pullCommand(cmd *cobra.Command, args []string) (err error) {
    46  	if engine.rawArgs.GetBool("warmup") {
    47  		for _, channel := range DefaultChannels {
    48  			if _, _, err = lookupImage(channel, normalizeVersion("latest"),
    49  				engine.rawArgs.GetBool("force"), false); err != nil {
    50  				return
    51  			}
    52  		}
    53  		return
    54  	}
    55  	c := normalizeChannelName(engine.rawArgs.GetString("channel"))
    56  	v := normalizeVersion(engine.rawArgs.GetString("version"))
    57  	_, _, err = lookupImage(c, v, engine.rawArgs.GetBool("force"), false)
    58  	return
    59  }
    60  
    61  func init() {
    62  	pullCmd.Flags().String("channel", "alpha", "CoreOS channel")
    63  	pullCmd.Flags().String("version", "latest", "CoreOS version")
    64  	pullCmd.Flags().BoolP("force", "f", false, "forces rebuild of local "+
    65  		"image, if already present")
    66  	pullCmd.Flags().BoolP("warmup", "w", false, "ensures that all channels "+
    67  		"are on their latest versions")
    68  	RootCmd.AddCommand(pullCmd)
    69  }
    70  
    71  func findLatestUpstream(channel string) (releaseInfo map[string]string, err error) {
    72  	var (
    73  		upstream = fmt.Sprintf("http://%s.%s/%s", channel,
    74  			"release.core-os.net", "amd64-usr/current/version.txt")
    75  		response *http.Response
    76  		s        *bufio.Scanner
    77  	)
    78  	releaseInfo = make(map[string]string)
    79  	if response, err = http.Get(upstream); err != nil {
    80  		// we're probably offline
    81  		return
    82  	}
    83  
    84  	defer response.Body.Close()
    85  	switch response.StatusCode {
    86  	case http.StatusOK, http.StatusNoContent:
    87  	default:
    88  		return releaseInfo, fmt.Errorf("failed fetching %s: HTTP status: %s",
    89  			upstream, response.Status)
    90  	}
    91  
    92  	s = bufio.NewScanner(response.Body)
    93  	s.Split(bufio.ScanLines)
    94  	for s.Scan() {
    95  		line := s.Text()
    96  		if eq := strings.Index(line, "="); eq >= 0 {
    97  			if key := strings.TrimSpace(line[:eq]); len(key) > 0 {
    98  				v := ""
    99  				if len(line) > eq {
   100  					v = strings.TrimSpace(line[eq+1:])
   101  				}
   102  				releaseInfo[key] = v
   103  			}
   104  		}
   105  	}
   106  	return
   107  }
   108  
   109  func lookupImage(channel, version string,
   110  	override, preferLocal bool) (a, b string, err error) {
   111  	var (
   112  		isLocal     bool
   113  		ll          map[string]semver.Versions
   114  		l           semver.Versions
   115  		releaseInfo map[string]string
   116  	)
   117  
   118  	if ll, err = localImages(); err != nil {
   119  		return channel, version, err
   120  	}
   121  	l = ll[channel]
   122  	if version == "latest" {
   123  		if preferLocal == true && len(l) > 0 {
   124  			version = l[l.Len()-1].String()
   125  		} else {
   126  			if releaseInfo, err = findLatestUpstream(channel); err != nil {
   127  				// as we're probably offline
   128  				if len(l) == 0 {
   129  					err = fmt.Errorf("offline and not a single locally image"+
   130  						"available for '%s' channel", channel)
   131  					return channel, version, err
   132  				}
   133  				version = l[l.Len()-1].String()
   134  			} else {
   135  				version = releaseInfo["COREOS_VERSION"]
   136  			}
   137  		}
   138  	}
   139  
   140  	for _, i := range l {
   141  		if version == i.String() {
   142  			isLocal = true
   143  			break
   144  		}
   145  	}
   146  	if isLocal && !override {
   147  		log.Printf("%s/%s already available on your system\n", channel, version)
   148  		return channel, version, err
   149  	}
   150  	return localize(channel, version)
   151  }
   152  
   153  func localize(channel, version string) (a string, b string, err error) {
   154  	var files map[string]string
   155  	destination := fmt.Sprintf("%s/%s/%s", engine.imageDir,
   156  		channel, version)
   157  
   158  	if err = os.MkdirAll(destination, 0755); err != nil {
   159  		return channel, version, err
   160  	}
   161  	if files, err = downloadAndVerify(channel, version); err != nil {
   162  		return channel, version, err
   163  	}
   164  	defer func() {
   165  		for _, location := range files {
   166  			if e := os.RemoveAll(filepath.Dir(location)); e != nil {
   167  				log.Println(e)
   168  			}
   169  		}
   170  	}()
   171  	for fn, location := range files {
   172  		// OEMify
   173  		if strings.HasSuffix(fn, "cpio.gz") {
   174  			var (
   175  				i, temp *os.File
   176  				r       *image.Reader
   177  				w       *image.Writer
   178  			)
   179  
   180  			if i, err = os.Open(location); err != nil {
   181  				return
   182  			}
   183  			defer i.Close()
   184  
   185  			if r, err = image.NewReader(i); err != nil {
   186  				return
   187  			}
   188  			defer r.Close()
   189  
   190  			if temp, err = ioutil.TempFile(engine.tmpDir, "coreos"); err != nil {
   191  				return
   192  			}
   193  			defer temp.Close()
   194  
   195  			if w, err = image.NewWriter(temp); err != nil {
   196  				return
   197  			}
   198  
   199  			for _, d := range []string{"usr", "usr/share", "usr/share/oem",
   200  				"usr/share/oem/bin"} {
   201  				if err = w.WriteDir(d, 0755); err != nil {
   202  					return
   203  				}
   204  			}
   205  
   206  			if err = w.WriteToFile(bytes.NewBufferString(CoreOEMsetupBootstrap),
   207  				"usr/share/oem/cloud-config.yml", 0644); err != nil {
   208  				return
   209  			}
   210  
   211  			if err = w.WriteToFile(bytes.NewBufferString(
   212  				strings.Replace(CoreOEMsetup, "@@version@@", version, -1)),
   213  				"usr/share/oem/xhyve.yml", 0644); err != nil {
   214  				return
   215  			}
   216  
   217  			if err = w.WriteToFile(bytes.NewBufferString(CoreOEMsetupEnv),
   218  				"usr/share/oem/bin/coreos-setup-environment",
   219  				0755); err != nil {
   220  				return
   221  			}
   222  			defer w.Close()
   223  
   224  			if err = image.Copy(w, r); err != nil {
   225  				return
   226  			}
   227  			if err = os.Rename(temp.Name(), location); err != nil {
   228  				return
   229  			}
   230  
   231  		}
   232  		if err = os.Rename(location,
   233  			fmt.Sprintf("%s/%s", destination, fn)); err != nil {
   234  			return channel, version, err
   235  		}
   236  	}
   237  	if err = normalizeOnDiskPermissions(destination); err == nil {
   238  		log.Printf("%s/%s ready\n", channel, version)
   239  	}
   240  	return channel, version, err
   241  }