123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- package control
- import (
- "fmt"
- "io/ioutil"
- "net/http"
- "net/url"
- "os"
- "strings"
- "golang.org/x/net/context"
- yaml "github.com/cloudfoundry-incubator/candiedyaml"
- "github.com/rancher/os/log"
- "github.com/codegangsta/cli"
- dockerClient "github.com/docker/engine-api/client"
- composeConfig "github.com/docker/libcompose/config"
- "github.com/docker/libcompose/project/options"
- "github.com/rancher/os/cmd/power"
- "github.com/rancher/os/compose"
- "github.com/rancher/os/config"
- "github.com/rancher/os/docker"
- )
- type Images struct {
- Current string `yaml:"current,omitempty"`
- Available []string `yaml:"available,omitempty"`
- }
- func osSubcommands() []cli.Command {
- return []cli.Command{
- {
- Name: "upgrade",
- Usage: "upgrade to latest version",
- Action: osUpgrade,
- Flags: []cli.Flag{
- cli.BoolFlag{
- Name: "stage, s",
- Usage: "Only stage the new upgrade, don't apply it",
- },
- cli.StringFlag{
- Name: "image, i",
- Usage: "upgrade to a certain image",
- },
- cli.BoolFlag{
- Name: "force, f",
- Usage: "do not prompt for input",
- },
- cli.BoolFlag{
- Name: "no-reboot",
- Usage: "do not reboot after upgrade",
- },
- cli.BoolFlag{
- Name: "kexec, k",
- Usage: "reboot using kexec",
- },
- cli.StringFlag{
- Name: "append",
- Usage: "append additional kernel parameters",
- },
- cli.BoolFlag{
- Name: "upgrade-console",
- Usage: "upgrade console even if persistent",
- },
- },
- },
- {
- Name: "list",
- Usage: "list the current available versions",
- Action: osMetaDataGet,
- },
- {
- Name: "version",
- Usage: "show the currently installed version",
- Action: osVersion,
- },
- }
- }
- // TODO: this and the getLatestImage should probably move to utils/network and be suitably cached.
- func getImages() (*Images, error) {
- upgradeURL, err := getUpgradeURL()
- if err != nil {
- return nil, err
- }
- var body []byte
- if strings.HasPrefix(upgradeURL, "/") {
- body, err = ioutil.ReadFile(upgradeURL)
- if err != nil {
- return nil, err
- }
- } else {
- u, err := url.Parse(upgradeURL)
- if err != nil {
- return nil, err
- }
- q := u.Query()
- q.Set("current", config.Version)
- u.RawQuery = q.Encode()
- upgradeURL = u.String()
- resp, err := http.Get(upgradeURL)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- body, err = ioutil.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- }
- return parseBody(body)
- }
- func osMetaDataGet(c *cli.Context) error {
- images, err := getImages()
- if err != nil {
- log.Fatal(err)
- }
- client, err := docker.NewSystemClient()
- if err != nil {
- log.Fatal(err)
- }
- cfg := config.LoadConfig()
- runningName := cfg.Rancher.Upgrade.Image + ":" + config.Version
- foundRunning := false
- for i := len(images.Available) - 1; i >= 0; i-- {
- image := images.Available[i]
- _, _, err := client.ImageInspectWithRaw(context.Background(), image, false)
- local := "local"
- if dockerClient.IsErrImageNotFound(err) {
- local = "remote"
- }
- available := "available"
- if image == images.Current {
- available = "latest"
- }
- var running string
- if image == runningName {
- foundRunning = true
- running = "running"
- }
- fmt.Println(image, local, available, running)
- }
- if !foundRunning {
- fmt.Println(config.Version, "running")
- }
- return nil
- }
- func getLatestImage() (string, error) {
- images, err := getImages()
- if err != nil {
- return "", err
- }
- return images.Current, nil
- }
- func osUpgrade(c *cli.Context) error {
- image := c.String("image")
- if image == "" {
- var err error
- image, err = getLatestImage()
- if err != nil {
- log.Fatal(err)
- }
- if image == "" {
- log.Fatal("Failed to find latest image")
- }
- }
- if c.Args().Present() {
- log.Fatalf("invalid arguments %v", c.Args())
- }
- if err := startUpgradeContainer(image, c.Bool("stage"), c.Bool("force"), !c.Bool("no-reboot"), c.Bool("kexec"), c.Bool("upgrade-console"), c.String("append")); err != nil {
- log.Fatal(err)
- }
- return nil
- }
- func osVersion(c *cli.Context) error {
- fmt.Println(config.Version)
- return nil
- }
- func startUpgradeContainer(image string, stage, force, reboot, kexec bool, upgradeConsole bool, kernelArgs string) error {
- command := []string{
- "-t", "rancher-upgrade",
- "-r", config.Version,
- }
- if kexec {
- command = append(command, "--kexec")
- }
- kernelArgs = strings.TrimSpace(kernelArgs)
- if kernelArgs != "" {
- command = append(command, "-a", kernelArgs)
- }
- if upgradeConsole {
- if err := config.Set("rancher.force_console_rebuild", true); err != nil {
- log.Fatal(err)
- }
- }
- fmt.Printf("Upgrading to %s\n", image)
- confirmation := "Continue"
- imageSplit := strings.Split(image, ":")
- if len(imageSplit) > 1 && imageSplit[1] == config.Version+config.Suffix {
- confirmation = fmt.Sprintf("Already at version %s. Continue anyway", imageSplit[1])
- }
- if !force && !yes(confirmation) {
- os.Exit(1)
- }
- container, err := compose.CreateService(nil, "os-upgrade", &composeConfig.ServiceConfigV1{
- LogDriver: "json-file",
- Privileged: true,
- Net: "host",
- Pid: "host",
- Image: image,
- Labels: map[string]string{
- config.ScopeLabel: config.System,
- },
- Command: command,
- })
- if err != nil {
- return err
- }
- client, err := docker.NewSystemClient()
- if err != nil {
- return err
- }
- // Only pull image if not found locally
- if _, _, err := client.ImageInspectWithRaw(context.Background(), image, false); err != nil {
- if err := container.Pull(context.Background()); err != nil {
- return err
- }
- }
- if !stage {
- // If there is already an upgrade container, delete it
- // Up() should to this, but currently does not due to a bug
- if err := container.Delete(context.Background(), options.Delete{}); err != nil {
- return err
- }
- if err := container.Up(context.Background(), options.Up{}); err != nil {
- return err
- }
- if err := container.Log(context.Background(), true); err != nil {
- return err
- }
- if err := container.Delete(context.Background(), options.Delete{}); err != nil {
- return err
- }
- if reboot && (force || yes("Continue with reboot")) {
- log.Info("Rebooting")
- power.Reboot()
- }
- }
- return nil
- }
- func parseBody(body []byte) (*Images, error) {
- update := &Images{}
- err := yaml.Unmarshal(body, update)
- if err != nil {
- return nil, err
- }
- return update, nil
- }
- func getUpgradeURL() (string, error) {
- cfg := config.LoadConfig()
- return cfg.Rancher.Upgrade.URL, nil
- }
|