diff --git a/cmd/control/cli.go b/cmd/control/cli.go index 68f182c0..ab3a09b7 100644 --- a/cmd/control/cli.go +++ b/cmd/control/cli.go @@ -45,20 +45,7 @@ func Main() { { Name: "os", Usage: "operating system upgrade/downgrade", - Subcommands: []cli.Command{ - { - Name: "list", - Usage: "list available RancherOS versions and state", - }, - { - Name: "update", - Usage: "download the latest new version of RancherOS", - }, - { - Name: "activate", - Usage: "switch to a new version of RancherOS and reboot", - }, - }, + Subcommands: osSubcommands(), }, } diff --git a/cmd/control/config.go b/cmd/control/config.go index 09a489d6..81110914 100644 --- a/cmd/control/config.go +++ b/cmd/control/config.go @@ -186,7 +186,7 @@ func getOrSetVal(args string, data map[interface{}]interface{}, value interface{ for i, part := range parts { val, ok := data[part] last := i+1 == len(parts) - + if last && value != nil { if s, ok := value.(string); ok { value = config.DummyMarshall(s) diff --git a/cmd/control/os.go b/cmd/control/os.go new file mode 100644 index 00000000..f75f915b --- /dev/null +++ b/cmd/control/os.go @@ -0,0 +1,197 @@ +package control + +import ( + "bufio" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + + log "github.com/Sirupsen/logrus" + + "github.com/codegangsta/cli" + "github.com/rancherio/os/cmd/power" + "github.com/rancherio/os/config" + "github.com/rancherio/os/docker" +) + +var osChannels map[string]string + +const ( + osVersionsFile = "/var/lib/rancher/versions" +) + +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.StringFlag{ + Name: "channel, c", + Usage: "upgrade to the latest in a specific channel", + }, + }, + }, + { + Name: "list", + Usage: "list the current available versions", + Action: osMetaDataGet, + }, + { + Name: "rollback", + Usage: "rollback to the previous version", + Action: osRollback, + }, + } +} + +func osRollback(c *cli.Context) { + file, err := os.Open(osVersionsFile) + + if err != nil { + log.Fatal(err) + } + + fileReader := bufio.NewScanner(file) + line := " " + for ; line[len(line)-1:] != "*"; { + if !fileReader.Scan() { + log.Error("Current version not indicated in "+ osVersionsFile) + } + line = fileReader.Text() + } + if !fileReader.Scan() { + log.Error("already at earliest version, please choose a version specifically using upgrade --image") + } + line = fileReader.Text() + //TODO: process string if required + + startUpgradeContainer(line, false) +} + +func osMetaDataGet(c *cli.Context) { + osChannel, ok := getChannelUrl("meta"); if !ok { + log.Fatal("unrecognized channel meta") + } + resp, err := http.Get(osChannel) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + fmt.Print(parseBody(body, osChannel)) +} + +func osUpgrade(c *cli.Context) { + channel := c.String("channel") + + image := c.String("image") + + if image == "" { + var err error + image, err = getLatestImage(channel) + if err != nil { + log.Fatal(err) + } + } + startUpgradeContainer(image, c.Bool("stage")) +} + +func startUpgradeContainer(image string, stage bool) { + container := docker.NewContainer(config.DOCKER_SYSTEM_HOST, &config.ContainerConfig{ + Cmd: "--name=upgrade " + + "--privileged " + + "--net=host " + + "--ipc=host " + + "--pid=host " + + "-v=/var:/var " + + "--volumes-from=system-volumes " + + image, + }).Stage() + + if container.Err != nil { + log.Fatal(container.Err) + } + + if !stage { + container.StartAndWait() + if container.Err != nil { + log.Fatal(container.Err) + } + power.Reboot() + } +} + +func getLatestImage(channel string) (string, error) { + data, err := getConfigData() + + if err != nil { + return "", err + } + + var pivot string + + if pivot == "" { + val := getOrSetVal("os_upgrade_channel", data, nil) + + if val == nil { + return "", errors.New("os_upgrade_channel is not set") + } + + switch currentChannel := val.(type) { + case string: + pivot = currentChannel + default: + return "", errors.New("invalid format of rancherctl config get os_upgrade_channel") + } + } else { + pivot = channel + } + osChannel, ok := getChannelUrl(pivot); if !ok { + return "", errors.New("unrecognized channel " + pivot) + } + resp, err := http.Get(osChannel) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + return parseBody(body, osChannel), nil +} + +func parseBody(body []byte, channel string) string { + // just going to assume that the response is the image name + // can change it later based on server response design + return string(body) +} + +func getChannelUrl(channel string) (string, bool) { + if osChannels == nil { + osChannels = map[string]string { + "stable" : "", + "alpha" : "", + "beta" : "", + "meta" : "", + } + } + channel, ok := osChannels[channel]; + return channel, ok +} + diff --git a/config/config.go b/config/config.go index 040ef376..c30930a4 100644 --- a/config/config.go +++ b/config/config.go @@ -46,6 +46,7 @@ type Config struct { RescueContainer *ContainerConfig `yaml:"rescue_container,omitempty"` State ConfigState `yaml:"state,omitempty"` Userdocker UserDockerInfo `yaml:"userdocker,omitempty"` + OsUpgradeChannel string `yaml:"os_upgrade_channel,omitempty"` SystemContainers []ContainerConfig `yaml:"system_containers,omitempty"` SystemDockerArgs []string `yaml:"system_docker_args,flow,omitempty"` Modules []string `yaml:"modules,omitempty"`