<commit_before>package main

import (
  "strings"
  "log"
  // "fmt"
//  "regexp"
  "bytes"
  "encoding/json"
  "os"
  "time"
  "strconv"
  "regexp"
  "github.com/lookify/town/cluster"
  dockerapi "github.com/fsouza/go-dockerclient"
)

var (
  scaleNumRegexp, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
//  SCALE_TOTAL_REG, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
  hostsRegexp, _ = regexp.Compile("\\$\\{(.+)_HOSTS\\}")
)

// Town describe cluster and docker clients.
type Town struct {
  cluster *cluster.Cluster
  docker *dockerapi.Client // TODO change to multiple clients
}

// NewTown create new town with default values
func NewTown() *Town {
  return &Town{
    cluster: nil,
    docker: nil,
  }
}

// ReadFile - read town configuration fail in current direcotry or /etc/town (ext .yml)
func (t *Town) ReadFile(name string) {
  var pathLocs = [...]string{
    name + ".yml",
    "/etc/town/" + name + ".yml",
  }

  for _, path := range pathLocs {
    if _, err := os.Stat(path); err == nil {
      t.cluster = cluster.NewCluster(path)
      t.cluster.ReadFile()
      return
    }
  }

  log.Println("ERROR: Could not find file ", name, ".yml")
}

// Connect - connect to docker hosts.
func (t *Town) Connect() {
  endpoint := t.cluster.Application.Docker.Hosts[0] // at the moment use only first
  log.Println("Using Docker API endpont: ", endpoint)
  docker, err := dockerapi.NewClient( endpoint )
  if err != nil {
    log.Println("Can't connect to the docker")
  }
  t.docker = docker
}

// Provision running containers.
func (t *Town) Provision(checkChanged bool) {
  // update containers
  for _, node := range t.cluster.Nodes {
    var buf bytes.Buffer
    var image = strings.Split(node.Container.Image, ":")

    opts := dockerapi.PullImageOptions{
        Repository: image[0],
        // Registry:     "docker.tsuru.io",
        // Tag:          "latest",
        OutputStream: &buf,
    }

    if len(image) > 1 {
      opts.Tag = image[1]
    }
    err := t.docker.PullImage(opts, dockerapi.AuthConfiguration{});
    if err != nil {
      log.Println("Could not pull image ", image)
    }
  }

  allContainers, err := t.docker.ListContainers(dockerapi.ListContainersOptions{
    All: true,
  })
  if err == nil {
    for _, listing := range allContainers {
      container, err := t.docker.InspectContainer(listing.ID)
      if err == nil {
        name := container.Name[1:]
        node, index := t.cluster.FindNodeByName(name)
        if node != nil && index > 0 {
          if node.Container.Exist == nil {
            node.Container.Exist = []cluster.ExistContainer{}
          }
          runningContainer := cluster.NewExistContainer(listing.ID, name, index, container.State.Running)
          runningContainer.Pid = container.State.Pid
          runningContainer.User = container.Config.User
          if checkChanged {
            node.Container.Changed = t.isChangedImage(node, container)
          } else {
            node.Container.Changed = true
          }
          node.Container.Exist = append(node.Container.Exist, runningContainer)
        }
      } else {
        log.Println("[ERROR] Unable to inspect container:", listing.ID[:12], err)
      }
    }

    if checkChanged {
      t.cluster.AddChangeDependant()
    }
  } else {
    log.Println("[ERROR] Can't start provision")
  }
}

// Info - print current cluster information.
func (t *Town) Info() {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    log.Print("Node ", node.Container.Name, " image ")
	  if node.Container.Changed {
      log.Print("(Changed)")
    }
	  log.Println(": ", node.Container.Image)
    for _, container := range node.Container.Exist {
      log.Print("      ", container.Name, "\t")
  	  if container.Running {
  	    log.Println("Running")
  	  } else {
  	    log.Print("Stoped")
  	  }
    }
  }
}

/**
 * Check node and running container for changes.
 * TODO: add cache to docker call.
 **/
func (t *Town) isChangedImage(node *cluster.Node, container *dockerapi.Container) bool {
  var imageName = container.Image
  image , error := t.docker.InspectImage(imageName)
  if error == nil {
    secondImage , secondError := t.docker.InspectImage(node.Container.Image)
    if secondError == nil {
      return secondImage.Created.After(image.Created)
    }
  }
  log.Println("[ERROR] Could not inspect image ", node.Container.Name)
  return false
}

// StopContainers - stop all containers or only containers with changed images.
func (t *Town) StopContainers(checkChanged bool) {
  log.Println("Stop...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        if container.Running {
          err := t.docker.StopContainer(container.ID, 10)
          if err == nil {
            log.Println("   -  ", container.Name)
          } else {
            log.Println("   -  ", container.Name, " failed ", err)
          }
        }
      }
    }
  }
  log.Println("=============================")
}

// RemoveContainers - remove container form local repository.
func (t *Town) RemoveContainers(checkChanged bool) {
  log.Println("Remove...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        err := t.docker.RemoveContainer(dockerapi.RemoveContainerOptions{
          ID: container.ID,
          RemoveVolumes: false,
        })
        if err == nil {
          log.Println("   -  ", container.Name)
        } else {
          log.Println("   -  ", container.Name, " failed ", err)
        }
      }
    }
  }
  log.Println("=============================")
}

// CreateContainer - create container.
func (t *Town) CreateContainer(node *cluster.Node, index int) (string, string, string) {
  containerName := node.Container.Name + "-" + strconv.Itoa(index)

  log.Println("   -  ", containerName)

  node.Container.Hostname = containerName // ?? Help !!!!

  env :=  make([]string, 0, cap(node.Container.Environment))
  for _, e := range node.Container.Environment {
    env = append(env, t.exec(e, index))
  }

  volumes := make(map[string]struct{})
  binds := make([]string, 0, cap(node.Container.Volumes))
  if len(node.Container.Volumes) > 0 {
    for _, volume := range node.Container.Volumes {
      volume = t.exec(volume, index)
      vol := strings.Split(volume, ":")
      if len(vol) > 1 {
        volumes[vol[1]] = struct{}{}
      } else {
        volumes[vol[0]] = struct{}{}
      }
      binds = append(binds, volume)
    }
  }

  dockerConfig := dockerapi.Config{
      Image: node.Container.Image,
      Hostname: node.Container.Hostname,
      PortSpecs: node.Container.Ports,
      Env: env,
      Volumes: volumes,

      AttachStdout: false,
      AttachStdin: false,
      AttachStderr: false,

      Tty: false,

      //Cmd: []
  }

  if len(node.Container.Command) > 0 {
    cmd := t.exec(node.Container.Command, index)
    dockerConfig.Cmd = []string{ cmd }
  }

  // just info
  //for _, l := range node.status.links {
  //  log.Println("     * ", l)
  //}

  // create links
  links := t.cluster.GetLinks(node)

  portBindings := map[dockerapi.Port][]dockerapi.PortBinding{}
  // create ports
  for _, ports := range node.Container.Ports {

    port := strings.Split(ports, ":")
    var p dockerapi.Port

    if len(port) > 1 {
      p = dockerapi.Port(port[1] + "/tcp")
    } else {
      p = dockerapi.Port(port[0] + "/tcp")
    }

    if portBindings[p] == nil {
      portBindings[p] = [] dockerapi.PortBinding {}
    }

    portBindings[p] = append(portBindings[p], dockerapi.PortBinding{
      HostIP: "",
      HostPort: port[0],
    })
  }

  hostConfig := dockerapi.HostConfig{
    Binds: binds,
    Links: links, //, [],
    PortBindings: portBindings,
    NetworkMode: "bridge",
    PublishAllPorts: false,
    Privileged: node.Container.Privileged,
  }

  opts := dockerapi.CreateContainerOptions{Name: containerName, Config: &dockerConfig, HostConfig: &hostConfig}
  container, err := t.docker.CreateContainer(opts)
  if err == nil {
    retry := 5
    for retry > 0 {
      error := t.docker.StartContainer(container.ID, &hostConfig)
      if error != nil {
        // log.Println("start error: ", error);

        out, err := json.Marshal(container)
        if err != nil {
            panic (err)
        }
        // fmt.Println(string(out))

        retry--;
        if retry == 0 {
          log.Println(" Start failed after 5 retries: ", string(out))
        }
        // log.Println("retry: ", retry);
      } else {
        inspect, inspectError := t.docker.InspectContainer(container.ID)
        if inspectError == nil {
          //links = append(links, inspect.NetworkSettings.IPAddress + "  " + containerName)
          //ids = append(ids, container.ID)
          return container.ID, inspect.NetworkSettings.IPAddress + "  " + containerName, containerName
        }

        log.Println("Inpect ", container.ID, " error ", inspectError)

        //retry = 0
        break;
      }
    }
  } else {
    log.Println("Create container ", containerName, " error: ", err);
  }

  return "", "", ""
}

// CreateContainers - create list of containers.
func (t *Town) CreateContainers(checkChanged bool) {
  log.Println("Create...")
  for _, node := range t.cluster.Nodes {

    if !checkChanged || node.Container.Changed {
      ids := make([]string, 0, node.Container.Scale )

      hosts := make([]string, 0, node.Container.Scale)

      log.Println(node.Container.Name, "  image: ", node.Container.Image)
      for i := 1; i <= node.Container.Scale; i++ {

        _, host, containerName := t.CreateContainer(node, i) //id

        if len(node.Container.Validate) > 0 {
          t.bashCommand(containerName, node.Container.Validate)
        }

        ids = append(ids, containerName)
        hosts = append(hosts, host)
      }

      if len(ids) > 1 {
        for index, id := range ids {
          var buffer bytes.Buffer

          buffer.WriteString("echo -e '")
          for i := 0; i < len(hosts); i++ {
            if i != index {
              buffer.WriteString("\n")
              buffer.WriteString(hosts[i])
            }
          }
          buffer.WriteString("' >> /etc/hosts; touch /tmp/host-generated")
          t.bashCommand(id, buffer.String() )
        }
      }

      time.Sleep(1000 * time.Millisecond)
    } else if len(node.Container.Exist) <= node.Container.Scale {
      log.Println(node.Container.Name, "  image: ", node.Container.Image, "  ", node.Container.Scale)
      /*
      var create [ node.Container.Scale ]bool
      for i := 0; i < node.Container.Scale; i++ {
        create[i] = true
      }
      for _, container := range node.Container.Exist {
        if container.Running {
          create[container.Index - 1] = false;
        }
      }

      for i := 0; i < node.Container.Scale; i++ {
        if create[i] {
          _, _, containerName := t.CreateContainer(node, i + 1)
          // TODO add hosts
          if len(node.Container.Validate) > 0 {
            t.bashCommand(containerName, node.Container.Validate)
          }
        }
      }
      */
    }
  }
}

// bashCommand - execute bash command inside container.
func (t *Town) bashCommand(id string, command string)  {
  config := dockerapi.CreateExecOptions{
    Container:    id,
    AttachStdin:  false,
    AttachStdout: false,
    AttachStderr: false,
    Tty:          false,
    Cmd:          []string{"bash", "-c", command},
  }
  execObj, err := t.docker.CreateExec(config)
  if err == nil {
    startConfig := dockerapi.StartExecOptions{
      Detach: false,
    }
    err = t.docker.StartExec(execObj.ID, startConfig)
    if err != nil {
      log.Println("Container ", id, " command failed with error: ", err, "\n", command)
    }
  } else {
    log.Println("Container ", id, " command failed with error: ", err, "\n", command)
  }
}

func (t *Town) exec(text string, scale int) string {
  replace := strings.Replace(text, "${SCALE_NUM}", strconv.Itoa(scale), -1)
  match := scaleNumRegexp.FindAllStringSubmatch(replace, -1)
  hostMatch := hostsRegexp.FindAllStringSubmatch(replace, -1)
  if len(match) > 0 {
    if len(match[0]) > 1 {
      nums := strings.Split(match[0][1], ",")
      if len(nums) > (scale - 1) {
        replace = strings.Replace(replace, match[0][0], nums[scale - 1], -1)
      }
    }
  }
  if len(hostMatch) > 0 {
    if len(hostMatch[0]) > 1 {
      //nums := strings.Split(, ",")
      name := strings.ToLower(hostMatch[0][1])
      node := t.cluster.FindNodeByID(name)

      var buffer bytes.Buffer
      for i := 1; i <= node.Container.Scale; i++ {
        buffer.WriteString(name)
        buffer.WriteString("-")
        buffer.WriteString(strconv.Itoa( i ))
        if i != node.Container.Scale {
          buffer.WriteString(",")
        }
      }
      replace = strings.Replace(replace, hostMatch[0][0], buffer.String(), -1)
    }
  }
  return replace
}
<commit_msg>add scale<commit_after>package main

import (
  "strings"
  "log"
  // "fmt"
//  "regexp"
  "bytes"
  "encoding/json"
  "os"
  "time"
  "strconv"
  "regexp"
  "github.com/lookify/town/cluster"
  dockerapi "github.com/fsouza/go-dockerclient"
)

var (
  scaleNumRegexp, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
//  SCALE_TOTAL_REG, _ = regexp.Compile("\\$\\{SCALE_NUM:(.+)\\}")
  hostsRegexp, _ = regexp.Compile("\\$\\{(.+)_HOSTS\\}")
)

// Town describe cluster and docker clients.
type Town struct {
  cluster *cluster.Cluster
  docker *dockerapi.Client // TODO change to multiple clients
}

// NewTown create new town with default values
func NewTown() *Town {
  return &Town{
    cluster: nil,
    docker: nil,
  }
}

// ReadFile - read town configuration fail in current direcotry or /etc/town (ext .yml)
func (t *Town) ReadFile(name string) {
  var pathLocs = [...]string{
    name + ".yml",
    "/etc/town/" + name + ".yml",
  }

  for _, path := range pathLocs {
    if _, err := os.Stat(path); err == nil {
      t.cluster = cluster.NewCluster(path)
      t.cluster.ReadFile()
      return
    }
  }

  log.Println("ERROR: Could not find file ", name, ".yml")
}

// Connect - connect to docker hosts.
func (t *Town) Connect() {
  endpoint := t.cluster.Application.Docker.Hosts[0] // at the moment use only first
  log.Println("Using Docker API endpont: ", endpoint)
  docker, err := dockerapi.NewClient( endpoint )
  if err != nil {
    log.Println("Can't connect to the docker")
  }
  t.docker = docker
}

// Provision running containers.
func (t *Town) Provision(checkChanged bool) {
  // update containers
  for _, node := range t.cluster.Nodes {
    var buf bytes.Buffer
    var image = strings.Split(node.Container.Image, ":")

    opts := dockerapi.PullImageOptions{
        Repository: image[0],
        // Registry:     "docker.tsuru.io",
        // Tag:          "latest",
        OutputStream: &buf,
    }

    if len(image) > 1 {
      opts.Tag = image[1]
    }
    err := t.docker.PullImage(opts, dockerapi.AuthConfiguration{});
    if err != nil {
      log.Println("Could not pull image ", image)
    }
  }

  allContainers, err := t.docker.ListContainers(dockerapi.ListContainersOptions{
    All: true,
  })
  if err == nil {
    for _, listing := range allContainers {
      container, err := t.docker.InspectContainer(listing.ID)
      if err == nil {
        name := container.Name[1:]
        node, index := t.cluster.FindNodeByName(name)
        if node != nil && index > 0 {
          if node.Container.Exist == nil {
            node.Container.Exist = []cluster.ExistContainer{}
          }
          runningContainer := cluster.NewExistContainer(listing.ID, name, index, container.State.Running)
          runningContainer.Pid = container.State.Pid
          runningContainer.User = container.Config.User
          if checkChanged {
            node.Container.Changed = t.isChangedImage(node, container)
          } else {
            node.Container.Changed = true
          }
          node.Container.Exist = append(node.Container.Exist, runningContainer)
        }
      } else {
        log.Println("[ERROR] Unable to inspect container:", listing.ID[:12], err)
      }
    }

    if checkChanged {
      t.cluster.AddChangeDependant()
    }
  } else {
    log.Println("[ERROR] Can't start provision")
  }
}

// Info - print current cluster information.
func (t *Town) Info() {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    log.Print("Node ", node.Container.Name, " image ")
	  if node.Container.Changed {
      log.Print("(Changed)")
    }
	  log.Println(": ", node.Container.Image)
    for _, container := range node.Container.Exist {
      log.Print("      ", container.Name, "\t")
  	  if container.Running {
  	    log.Println("Running")
  	  } else {
  	    log.Print("Stoped")
  	  }
    }
  }
}

/**
 * Check node and running container for changes.
 * TODO: add cache to docker call.
 **/
func (t *Town) isChangedImage(node *cluster.Node, container *dockerapi.Container) bool {
  var imageName = container.Image
  image , error := t.docker.InspectImage(imageName)
  if error == nil {
    secondImage , secondError := t.docker.InspectImage(node.Container.Image)
    if secondError == nil {
      return secondImage.Created.After(image.Created)
    }
  }
  log.Println("[ERROR] Could not inspect image ", node.Container.Name)
  return false
}

// StopContainers - stop all containers or only containers with changed images.
func (t *Town) StopContainers(checkChanged bool) {
  log.Println("Stop...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        if container.Running {
          err := t.docker.StopContainer(container.ID, 10)
          if err == nil {
            log.Println("   -  ", container.Name)
          } else {
            log.Println("   -  ", container.Name, " failed ", err)
          }
        }
      }
    }
  }
  log.Println("=============================")
}

// RemoveContainers - remove container form local repository.
func (t *Town) RemoveContainers(checkChanged bool) {
  log.Println("Remove...")
  //for node := range t.cluster.nodes {
  for i := len(t.cluster.Nodes) - 1; i >= 0; i-- {
    node := t.cluster.Nodes[i]
    if (!checkChanged || node.Container.Changed) && len(node.Container.Exist) > 0 {
      for _, container := range node.Container.Exist {
        err := t.docker.RemoveContainer(dockerapi.RemoveContainerOptions{
          ID: container.ID,
          RemoveVolumes: false,
        })
        if err == nil {
          log.Println("   -  ", container.Name)
        } else {
          log.Println("   -  ", container.Name, " failed ", err)
        }
      }
    }
  }
  log.Println("=============================")
}

// CreateContainer - create container.
func (t *Town) CreateContainer(node *cluster.Node, index int) (string, string, string) {
  containerName := node.Container.Name + "-" + strconv.Itoa(index)

  log.Println("   -  ", containerName)

  node.Container.Hostname = containerName // ?? Help !!!!

  env :=  make([]string, 0, cap(node.Container.Environment))
  for _, e := range node.Container.Environment {
    env = append(env, t.exec(e, index))
  }

  volumes := make(map[string]struct{})
  binds := make([]string, 0, cap(node.Container.Volumes))
  if len(node.Container.Volumes) > 0 {
    for _, volume := range node.Container.Volumes {
      volume = t.exec(volume, index)
      vol := strings.Split(volume, ":")
      if len(vol) > 1 {
        volumes[vol[1]] = struct{}{}
      } else {
        volumes[vol[0]] = struct{}{}
      }
      binds = append(binds, volume)
    }
  }

  dockerConfig := dockerapi.Config{
      Image: node.Container.Image,
      Hostname: node.Container.Hostname,
      PortSpecs: node.Container.Ports,
      Env: env,
      Volumes: volumes,

      AttachStdout: false,
      AttachStdin: false,
      AttachStderr: false,

      Tty: false,

      //Cmd: []
  }

  if len(node.Container.Command) > 0 {
    cmd := t.exec(node.Container.Command, index)
    dockerConfig.Cmd = []string{ cmd }
  }

  // just info
  //for _, l := range node.status.links {
  //  log.Println("     * ", l)
  //}

  // create links
  links := t.cluster.GetLinks(node)

  portBindings := map[dockerapi.Port][]dockerapi.PortBinding{}
  // create ports
  for _, ports := range node.Container.Ports {

    port := strings.Split(ports, ":")
    var p dockerapi.Port

    if len(port) > 1 {
      p = dockerapi.Port(port[1] + "/tcp")
    } else {
      p = dockerapi.Port(port[0] + "/tcp")
    }

    if portBindings[p] == nil {
      portBindings[p] = [] dockerapi.PortBinding {}
    }

    portBindings[p] = append(portBindings[p], dockerapi.PortBinding{
      HostIP: "",
      HostPort: port[0],
    })
  }

  hostConfig := dockerapi.HostConfig{
    Binds: binds,
    Links: links, //, [],
    PortBindings: portBindings,
    NetworkMode: "bridge",
    PublishAllPorts: false,
    Privileged: node.Container.Privileged,
  }

  opts := dockerapi.CreateContainerOptions{Name: containerName, Config: &dockerConfig, HostConfig: &hostConfig}
  container, err := t.docker.CreateContainer(opts)
  if err == nil {
    retry := 5
    for retry > 0 {
      error := t.docker.StartContainer(container.ID, &hostConfig)
      if error != nil {
        // log.Println("start error: ", error);

        out, err := json.Marshal(container)
        if err != nil {
            panic (err)
        }
        // fmt.Println(string(out))

        retry--;
        if retry == 0 {
          log.Println(" Start failed after 5 retries: ", string(out))
        }
        // log.Println("retry: ", retry);
      } else {
        inspect, inspectError := t.docker.InspectContainer(container.ID)
        if inspectError == nil {
          //links = append(links, inspect.NetworkSettings.IPAddress + "  " + containerName)
          //ids = append(ids, container.ID)
          return container.ID, inspect.NetworkSettings.IPAddress + "  " + containerName, containerName
        }

        log.Println("Inpect ", container.ID, " error ", inspectError)

        //retry = 0
        break;
      }
    }
  } else {
    log.Println("Create container ", containerName, " error: ", err);
  }

  return "", "", ""
}

// CreateContainers - create list of containers.
func (t *Town) CreateContainers(checkChanged bool) {
  log.Println("Create...")
  for _, node := range t.cluster.Nodes {

    if !checkChanged || node.Container.Changed {
      ids := make([]string, 0, node.Container.Scale )

      hosts := make([]string, 0, node.Container.Scale)

      log.Println(node.Container.Name, "  image: ", node.Container.Image)
      for i := 1; i <= node.Container.Scale; i++ {

        _, host, containerName := t.CreateContainer(node, i) //id

        if len(node.Container.Validate) > 0 {
          t.bashCommand(containerName, node.Container.Validate)
        }

        ids = append(ids, containerName)
        hosts = append(hosts, host)
      }

      if len(ids) > 1 {
        for index, id := range ids {
          var buffer bytes.Buffer

          buffer.WriteString("echo -e '")
          for i := 0; i < len(hosts); i++ {
            if i != index {
              buffer.WriteString("\n")
              buffer.WriteString(hosts[i])
            }
          }
          buffer.WriteString("' >> /etc/hosts; touch /tmp/host-generated")
          t.bashCommand(id, buffer.String() )
        }
      }

      time.Sleep(1000 * time.Millisecond)
    } else if len(node.Container.Exist) <= node.Container.Scale {
      log.Println(node.Container.Name, "  image: ", node.Container.Image, "  ", node.Container.Scale)
      val create = [ node.Container.Scale ]bool
      for i := 0; i < node.Container.Scale; i++ {
        create[i] = true
      }
      for _, container := range node.Container.Exist {
        if container.Running {
          create[container.Index - 1] = false;
        }
      }

      for i := 0; i < node.Container.Scale; i++ {
        if create[i] {
          _, _, containerName := t.CreateContainer(node, i + 1)
          // TODO add hosts
          if len(node.Container.Validate) > 0 {
            t.bashCommand(containerName, node.Container.Validate)
          }
        }
      }
    }
  }
}

// bashCommand - execute bash command inside container.
func (t *Town) bashCommand(id string, command string)  {
  config := dockerapi.CreateExecOptions{
    Container:    id,
    AttachStdin:  false,
    AttachStdout: false,
    AttachStderr: false,
    Tty:          false,
    Cmd:          []string{"bash", "-c", command},
  }
  execObj, err := t.docker.CreateExec(config)
  if err == nil {
    startConfig := dockerapi.StartExecOptions{
      Detach: false,
    }
    err = t.docker.StartExec(execObj.ID, startConfig)
    if err != nil {
      log.Println("Container ", id, " command failed with error: ", err, "\n", command)
    }
  } else {
    log.Println("Container ", id, " command failed with error: ", err, "\n", command)
  }
}

func (t *Town) exec(text string, scale int) string {
  replace := strings.Replace(text, "${SCALE_NUM}", strconv.Itoa(scale), -1)
  match := scaleNumRegexp.FindAllStringSubmatch(replace, -1)
  hostMatch := hostsRegexp.FindAllStringSubmatch(replace, -1)
  if len(match) > 0 {
    if len(match[0]) > 1 {
      nums := strings.Split(match[0][1], ",")
      if len(nums) > (scale - 1) {
        replace = strings.Replace(replace, match[0][0], nums[scale - 1], -1)
      }
    }
  }
  if len(hostMatch) > 0 {
    if len(hostMatch[0]) > 1 {
      //nums := strings.Split(, ",")
      name := strings.ToLower(hostMatch[0][1])
      node := t.cluster.FindNodeByID(name)

      var buffer bytes.Buffer
      for i := 1; i <= node.Container.Scale; i++ {
        buffer.WriteString(name)
        buffer.WriteString("-")
        buffer.WriteString(strconv.Itoa( i ))
        if i != node.Container.Scale {
          buffer.WriteString(",")
        }
      }
      replace = strings.Replace(replace, hostMatch[0][0], buffer.String(), -1)
    }
  }
  return replace
}
<|endoftext|>


<commit_before>package uvm

import (
	"fmt"

	"github.com/Microsoft/hcsshim/internal/guestrequest"
	"github.com/Microsoft/hcsshim/internal/requesttype"
	"github.com/Microsoft/hcsshim/internal/schema2"
	"github.com/sirupsen/logrus"
)

var (
	ErrNoAvailableLocation = fmt.Errorf("no available location")
	ErrNotAttached         = fmt.Errorf("not attached")
	ErrAlreadyAttached     = fmt.Errorf("already attached")
	ErrNoSCSIControllers   = fmt.Errorf("no SCSI controllers configured for this utility VM")
	ErrNoUvmParameter      = fmt.Errorf("invalid parameters - uvm parameter missing")
	ErrTooManyAttachments  = fmt.Errorf("too many SCSI attachments")
)

// allocateSCSI finds the next available slot on the
// SCSI controllers associated with a utility VM to use.
func (uvm *UtilityVM) allocateSCSI(hostPath string, uvmPath string) (int, int32, error) {
	uvm.m.Lock()
	defer uvm.m.Unlock()
	for controller, luns := range uvm.scsiLocations {
		for lun, si := range luns {
			if si.hostPath == "" {
				uvm.scsiLocations[controller][lun].hostPath = hostPath
				uvm.scsiLocations[controller][lun].uvmPath = uvmPath
				logrus.Debugf("uvm::allocateSCSI %d:%d %q %q", controller, lun, hostPath, uvmPath)
				return controller, int32(lun), nil

			}
		}
	}
	return -1, -1, ErrNoAvailableLocation
}

func (uvm *UtilityVM) deallocateSCSI(controller int, lun int32) error {
	uvm.m.Lock()
	defer uvm.m.Unlock()
	logrus.Debugf("uvm::deallocateSCSI %d:%d %+v", controller, lun, uvm.scsiLocations[controller][lun])
	uvm.scsiLocations[controller][lun] = scsiInfo{}

	return nil
}

// Lock must be held when calling this function
func (uvm *UtilityVM) findSCSIAttachment(findThisHostPath string) (int, int32, string, error) {
	for controller, luns := range uvm.scsiLocations {
		for lun, si := range luns {
			if si.hostPath == findThisHostPath {
				logrus.Debugf("uvm::findSCSIAttachment %d:%d %+v", controller, lun, si)
				return controller, int32(lun), si.uvmPath, nil
			}
		}
	}
	return -1, -1, "", ErrNotAttached
}

// AddSCSI adds a SCSI disk to a utility VM at the next available location.
//
// We are in control of everything ourselves. Hence we have ref-
// counting and so-on tracking what SCSI locations are available or used.
//
// hostPath is required
// uvmPath is optional.
//
// Returns the controller ID (0..3) and LUN (0..63) where the disk is attached.
func (uvm *UtilityVM) AddSCSI(hostPath string, uvmPath string) (int, int32, error) {
	controller := -1
	var lun int32 = -1
	if uvm == nil {
		return -1, -1, ErrNoUvmParameter
	}
	logrus.Debugf("uvm::AddSCSI id:%s hostPath:%s uvmPath:%s", uvm.id, hostPath, uvmPath)

	if uvm.scsiControllerCount == 0 {
		return -1, -1, ErrNoSCSIControllers
	}

	uvm.m.Lock()
	controller, lun, uvmPath, err := uvm.findSCSIAttachment(hostPath)
	if err == nil {
		uvm.m.Unlock()
		return -1, -1, ErrAlreadyAttached
	}
	uvm.m.Unlock()

	controller, lun, err = uvm.allocateSCSI(hostPath, uvmPath)
	if err != nil {
		return -1, -1, err
	}

	// TODO: Currently GCS doesn't support more than one SCSI controller. @jhowardmsft/@swernli. This will hopefully be fixed in GCS for RS5.
	// It will also require the GuestRequest to be extended in the call below to include the controller as well as the LUN.
	if controller > 0 {
		return -1, -1, ErrTooManyAttachments
	}

	// TODO: This is wrong. There's no way to hot-add a SCSI attachement currently. This is a HACK
	SCSIModification := &hcsschema.ModifySettingRequest{
		RequestType: requesttype.Add,
		Settings: hcsschema.Attachment{
			Path:  hostPath,
			Type_: "VirtualDisk",
		},
		ResourcePath: fmt.Sprintf("VirtualMachine/Devices/Scsi/%d/Attachments/%d", controller, lun),
	}

	if uvmPath != "" {
		if uvm.operatingSystem == "windows" {
			SCSIModification.GuestRequest = guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeMappedVirtualDisk,
				RequestType:  requesttype.Add,
				Settings: guestrequest.WCOWMappedVirtualDisk{
					ContainerPath: uvmPath,
					Lun:           lun,
					AttachOnly:    (uvmPath == ""),
					// TODO: Controller: uint8(controller), // TODO NOT IN HCS API CURRENTLY
				},
			}
		} else {
			SCSIModification.GuestRequest = guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeMappedVirtualDisk,
				RequestType:  requesttype.Add,
				Settings: guestrequest.LCOWMappedVirtualDisk{
					MountPath:  uvmPath,
					Lun:        uint8(lun),
					Controller: uint8(controller),
					ReadOnly:   false,
				},
			}
		}
	}

	if err := uvm.Modify(SCSIModification); err != nil {
		uvm.deallocateSCSI(controller, lun)
		return -1, -1, fmt.Errorf("uvm::AddSCSI: failed to modify utility VM configuration: %s", err)
	}
	logrus.Debugf("uvm::AddSCSI id:%s hostPath:%s added at %d:%d", uvm.id, hostPath, controller, lun)
	return controller, int32(lun), nil

}

// RemoveSCSI removes a SCSI disk from a utility VM. As an external API, it
// is "safe". Internal use can call removeSCSI.
func (uvm *UtilityVM) RemoveSCSI(hostPath string) error {
	uvm.m.Lock()
	defer uvm.m.Unlock()

	if uvm.scsiControllerCount == 0 {
		return ErrNoSCSIControllers
	}

	// Make sure is actually attached
	controller, lun, uvmPath, err := uvm.findSCSIAttachment(hostPath)
	if err != nil {
		return err
	}

	if err := uvm.removeSCSI(hostPath, uvmPath, controller, lun); err != nil {
		return fmt.Errorf("failed to remove SCSI disk %s from container %s: %s", hostPath, uvm.id, err)

	}
	return nil
}

// removeSCSI is the internally callable "unsafe" version of RemoveSCSI. The mutex
// MUST be held when calling this function.
func (uvm *UtilityVM) removeSCSI(hostPath string, uvmPath string, controller int, lun int32) error {
	logrus.Debugf("uvm::RemoveSCSI id:%s hostPath:%s", uvm.id, hostPath)
	scsiModification := &hcsschema.ModifySettingRequest{
		RequestType:  requesttype.Remove,
		ResourcePath: fmt.Sprintf("VirtualMachine/Devices/Scsi/%d/Attachments/%d", controller, lun),
	}

	// Include the GuestRequest so that the GCS ejects the disk cleanly if the disk was attached/mounted
	if uvmPath != "" {
		if uvm.operatingSystem == "windows" {
			scsiModification.GuestRequest = guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeMappedVirtualDisk,
				RequestType:  requesttype.Remove,
				Settings: guestrequest.WCOWMappedVirtualDisk{
					ContainerPath: uvmPath,
					Lun:           lun,
					// TODO: Controller: uint8(controller), // TODO NOT IN HCS API CURRENTLY
				},
			}
		} else {
			scsiModification.GuestRequest = guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeMappedVirtualDisk,
				RequestType:  requesttype.Remove,
				Settings: guestrequest.LCOWMappedVirtualDisk{
					MountPath:  uvmPath, // May be blank in attach-only
					Lun:        uint8(lun),
					Controller: uint8(controller),
				},
			}
		}
	}

	if err := uvm.Modify(scsiModification); err != nil {
		return err
	}
	uvm.scsiLocations[controller][lun] = scsiInfo{}
	logrus.Debugf("uvm::RemoveSCSI: Success %s removed from %s %d:%d", hostPath, uvm.id, controller, lun)
	return nil
}
<commit_msg>Remove old SCSI comment<commit_after>package uvm

import (
	"fmt"

	"github.com/Microsoft/hcsshim/internal/guestrequest"
	"github.com/Microsoft/hcsshim/internal/requesttype"
	"github.com/Microsoft/hcsshim/internal/schema2"
	"github.com/sirupsen/logrus"
)

var (
	ErrNoAvailableLocation = fmt.Errorf("no available location")
	ErrNotAttached         = fmt.Errorf("not attached")
	ErrAlreadyAttached     = fmt.Errorf("already attached")
	ErrNoSCSIControllers   = fmt.Errorf("no SCSI controllers configured for this utility VM")
	ErrNoUvmParameter      = fmt.Errorf("invalid parameters - uvm parameter missing")
	ErrTooManyAttachments  = fmt.Errorf("too many SCSI attachments")
)

// allocateSCSI finds the next available slot on the
// SCSI controllers associated with a utility VM to use.
func (uvm *UtilityVM) allocateSCSI(hostPath string, uvmPath string) (int, int32, error) {
	uvm.m.Lock()
	defer uvm.m.Unlock()
	for controller, luns := range uvm.scsiLocations {
		for lun, si := range luns {
			if si.hostPath == "" {
				uvm.scsiLocations[controller][lun].hostPath = hostPath
				uvm.scsiLocations[controller][lun].uvmPath = uvmPath
				logrus.Debugf("uvm::allocateSCSI %d:%d %q %q", controller, lun, hostPath, uvmPath)
				return controller, int32(lun), nil

			}
		}
	}
	return -1, -1, ErrNoAvailableLocation
}

func (uvm *UtilityVM) deallocateSCSI(controller int, lun int32) error {
	uvm.m.Lock()
	defer uvm.m.Unlock()
	logrus.Debugf("uvm::deallocateSCSI %d:%d %+v", controller, lun, uvm.scsiLocations[controller][lun])
	uvm.scsiLocations[controller][lun] = scsiInfo{}

	return nil
}

// Lock must be held when calling this function
func (uvm *UtilityVM) findSCSIAttachment(findThisHostPath string) (int, int32, string, error) {
	for controller, luns := range uvm.scsiLocations {
		for lun, si := range luns {
			if si.hostPath == findThisHostPath {
				logrus.Debugf("uvm::findSCSIAttachment %d:%d %+v", controller, lun, si)
				return controller, int32(lun), si.uvmPath, nil
			}
		}
	}
	return -1, -1, "", ErrNotAttached
}

// AddSCSI adds a SCSI disk to a utility VM at the next available location.
//
// We are in control of everything ourselves. Hence we have ref-
// counting and so-on tracking what SCSI locations are available or used.
//
// hostPath is required
// uvmPath is optional.
//
// Returns the controller ID (0..3) and LUN (0..63) where the disk is attached.
func (uvm *UtilityVM) AddSCSI(hostPath string, uvmPath string) (int, int32, error) {
	controller := -1
	var lun int32 = -1
	if uvm == nil {
		return -1, -1, ErrNoUvmParameter
	}
	logrus.Debugf("uvm::AddSCSI id:%s hostPath:%s uvmPath:%s", uvm.id, hostPath, uvmPath)

	if uvm.scsiControllerCount == 0 {
		return -1, -1, ErrNoSCSIControllers
	}

	uvm.m.Lock()
	controller, lun, uvmPath, err := uvm.findSCSIAttachment(hostPath)
	if err == nil {
		uvm.m.Unlock()
		return -1, -1, ErrAlreadyAttached
	}
	uvm.m.Unlock()

	controller, lun, err = uvm.allocateSCSI(hostPath, uvmPath)
	if err != nil {
		return -1, -1, err
	}

	// TODO: Currently GCS doesn't support more than one SCSI controller. @jhowardmsft/@swernli. This will hopefully be fixed in GCS for RS5.
	// It will also require the GuestRequest to be extended in the call below to include the controller as well as the LUN.
	if controller > 0 {
		return -1, -1, ErrTooManyAttachments
	}

	SCSIModification := &hcsschema.ModifySettingRequest{
		RequestType: requesttype.Add,
		Settings: hcsschema.Attachment{
			Path:  hostPath,
			Type_: "VirtualDisk",
		},
		ResourcePath: fmt.Sprintf("VirtualMachine/Devices/Scsi/%d/Attachments/%d", controller, lun),
	}

	if uvmPath != "" {
		if uvm.operatingSystem == "windows" {
			SCSIModification.GuestRequest = guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeMappedVirtualDisk,
				RequestType:  requesttype.Add,
				Settings: guestrequest.WCOWMappedVirtualDisk{
					ContainerPath: uvmPath,
					Lun:           lun,
					AttachOnly:    (uvmPath == ""),
					// TODO: Controller: uint8(controller), // TODO NOT IN HCS API CURRENTLY
				},
			}
		} else {
			SCSIModification.GuestRequest = guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeMappedVirtualDisk,
				RequestType:  requesttype.Add,
				Settings: guestrequest.LCOWMappedVirtualDisk{
					MountPath:  uvmPath,
					Lun:        uint8(lun),
					Controller: uint8(controller),
					ReadOnly:   false,
				},
			}
		}
	}

	if err := uvm.Modify(SCSIModification); err != nil {
		uvm.deallocateSCSI(controller, lun)
		return -1, -1, fmt.Errorf("uvm::AddSCSI: failed to modify utility VM configuration: %s", err)
	}
	logrus.Debugf("uvm::AddSCSI id:%s hostPath:%s added at %d:%d", uvm.id, hostPath, controller, lun)
	return controller, int32(lun), nil

}

// RemoveSCSI removes a SCSI disk from a utility VM. As an external API, it
// is "safe". Internal use can call removeSCSI.
func (uvm *UtilityVM) RemoveSCSI(hostPath string) error {
	uvm.m.Lock()
	defer uvm.m.Unlock()

	if uvm.scsiControllerCount == 0 {
		return ErrNoSCSIControllers
	}

	// Make sure is actually attached
	controller, lun, uvmPath, err := uvm.findSCSIAttachment(hostPath)
	if err != nil {
		return err
	}

	if err := uvm.removeSCSI(hostPath, uvmPath, controller, lun); err != nil {
		return fmt.Errorf("failed to remove SCSI disk %s from container %s: %s", hostPath, uvm.id, err)

	}
	return nil
}

// removeSCSI is the internally callable "unsafe" version of RemoveSCSI. The mutex
// MUST be held when calling this function.
func (uvm *UtilityVM) removeSCSI(hostPath string, uvmPath string, controller int, lun int32) error {
	logrus.Debugf("uvm::RemoveSCSI id:%s hostPath:%s", uvm.id, hostPath)
	scsiModification := &hcsschema.ModifySettingRequest{
		RequestType:  requesttype.Remove,
		ResourcePath: fmt.Sprintf("VirtualMachine/Devices/Scsi/%d/Attachments/%d", controller, lun),
	}

	// Include the GuestRequest so that the GCS ejects the disk cleanly if the disk was attached/mounted
	if uvmPath != "" {
		if uvm.operatingSystem == "windows" {
			scsiModification.GuestRequest = guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeMappedVirtualDisk,
				RequestType:  requesttype.Remove,
				Settings: guestrequest.WCOWMappedVirtualDisk{
					ContainerPath: uvmPath,
					Lun:           lun,
					// TODO: Controller: uint8(controller), // TODO NOT IN HCS API CURRENTLY
				},
			}
		} else {
			scsiModification.GuestRequest = guestrequest.GuestRequest{
				ResourceType: guestrequest.ResourceTypeMappedVirtualDisk,
				RequestType:  requesttype.Remove,
				Settings: guestrequest.LCOWMappedVirtualDisk{
					MountPath:  uvmPath, // May be blank in attach-only
					Lun:        uint8(lun),
					Controller: uint8(controller),
				},
			}
		}
	}

	if err := uvm.Modify(scsiModification); err != nil {
		return err
	}
	uvm.scsiLocations[controller][lun] = scsiInfo{}
	logrus.Debugf("uvm::RemoveSCSI: Success %s removed from %s %d:%d", hostPath, uvm.id, controller, lun)
	return nil
}
<|endoftext|>


<commit_before>package vm

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/grubby/grubby/ast"
	"github.com/grubby/grubby/interpreter/vm/builtins"
	builtin_errors "github.com/grubby/grubby/interpreter/vm/builtins/errors"
	"github.com/grubby/grubby/parser"
)

type vm struct {
	currentFilename string
	ObjectSpace     map[string]builtins.Value
	Globals         map[string]builtins.Value
	KnownSymbols    map[string]builtins.Value
}

type VM interface {
	Run(string) (builtins.Value, error)
	Get(string) (builtins.Value, error)
	MustGet(string) builtins.Value

	Set(string, builtins.Value)

	Symbols() map[string]builtins.Value
}

func NewVM(name string) VM {
	vm := &vm{
		currentFilename: name,
		Globals:         make(map[string]builtins.Value),
		ObjectSpace:     make(map[string]builtins.Value),
		KnownSymbols:    make(map[string]builtins.Value),
	}

	loadPath := builtins.NewArrayClass()
	vm.Globals["$LOAD_PATH"] = loadPath
	vm.Globals["$:"] = loadPath // FIXME: add a test that these are the same object

	objectClass := builtins.NewGlobalObjectClass()
	vm.ObjectSpace["Object"] = objectClass
	vm.ObjectSpace["Kernel"] = builtins.NewGlobalKernelClass()
	vm.ObjectSpace["File"] = builtins.NewFileClass()

	main := objectClass.(builtins.Class).New()
	main.AddMethod(builtins.NewMethod("to_s", func(args ...builtins.Value) (builtins.Value, error) {
		return builtins.NewString("main"), nil
	}))
	main.AddMethod(builtins.NewMethod("require", func(args ...builtins.Value) (builtins.Value, error) {
		fileName := args[0].(*builtins.StringValue).String()
		fileName = fileName[1 : len(fileName)-1]

		for _, pathStr := range loadPath.(*builtins.Array).Members() {
			path := pathStr.(*builtins.StringValue)
			fullPath := filepath.Join(path.String(), fileName+".rb")
			file, err := os.Open(fullPath)
			if err != nil {
				continue
			}

			contents, err := ioutil.ReadAll(file)

			if err == nil {
				originalName := vm.currentFilename
				defer func() {
					vm.currentFilename = originalName
				}()

				vm.currentFilename = file.Name()
				vm.Run(string(contents))
				return nil, nil
			}
		}

		err := fmt.Sprintf("LoadError: cannot load such file -- %s", fileName)
		return nil, builtin_errors.NewLoadError(err)

	}))
	main.AddMethod(builtins.NewMethod("puts", func(args ...builtins.Value) (builtins.Value, error) {
		for _, arg := range args {
			os.Stdout.Write([]byte(arg.String() + "\n"))
		}

		return nil, nil
	}))

	vm.ObjectSpace["main"] = main

	return vm
}

func (vm *vm) MustGet(key string) builtins.Value {
	val, ok := vm.ObjectSpace[key]
	if ok {
		return val
	}

	val, ok = vm.Globals[key]
	if ok {
		return val
	}

	return nil
}

func (vm *vm) Get(key string) (builtins.Value, error) {
	val, ok := vm.ObjectSpace[key]
	if ok {
		return val, nil
	}

	val, ok = vm.Globals[key]
	if ok {
		return val, nil
	}

	return nil, errors.New(fmt.Sprintf("'%s' is undefined", key))
}

func (vm *vm) Set(key string, value builtins.Value) {
	vm.ObjectSpace[key] = value
}

func (vm *vm) Symbols() map[string]builtins.Value {
	return vm.KnownSymbols
}

func (vm *vm) Run(input string) (builtins.Value, error) {
	lexer := parser.NewLexer(input)
	parser.RubyParse(lexer)

	main := vm.ObjectSpace["main"]
	return vm.executeWithContext(parser.Statements, main)
}

func (vm *vm) executeWithContext(statements []ast.Node, context builtins.Value) (builtins.Value, error) {
	var (
		returnValue builtins.Value
		returnErr   error
	)
	for _, statement := range statements {
		switch statement.(type) {
		case ast.FuncDecl:
			// FIXME: assumes for now this will only ever be at the top level
			// it seems like this should be replaced with context, but that's really context for calling methods, not
			// necessarily for defining new methods
			funcNode := statement.(ast.FuncDecl)
			method := builtins.NewMethod(funcNode.Name.Name, func(args ...builtins.Value) (builtins.Value, error) {
				return nil, nil
			})
			returnValue = method
			vm.ObjectSpace["Kernel"].AddPrivateMethod(method)
		case ast.SimpleString:
			returnValue = builtins.NewString(statement.(ast.SimpleString).Value)
		case ast.InterpolatedString:
			returnValue = builtins.NewString(statement.(ast.InterpolatedString).Value)
		case ast.ConstantInt:
			returnValue = builtins.NewInt(statement.(ast.ConstantInt).Value)
		case ast.ConstantFloat:
			returnValue = builtins.NewFloat(statement.(ast.ConstantFloat).Value)
		case ast.Symbol:
			name := statement.(ast.Symbol).Name
			maybe, ok := vm.KnownSymbols[name]
			if !ok {
				returnValue = builtins.NewSymbol(name)
				vm.KnownSymbols[name] = returnValue
			} else {
				returnValue = maybe
			}
		case ast.BareReference:
			name := statement.(ast.BareReference).Name
			maybe, ok := vm.ObjectSpace[name]
			if ok {
				returnValue = maybe
			} else {
				returnErr = builtin_errors.NewNameError(name, context.String(), context.Class().String())
			}
		case ast.CallExpression:
			var method builtins.Method
			callExpr := statement.(ast.CallExpression)
			method, err := context.Method(callExpr.Func.Name)

			if err != nil {
				err := builtin_errors.NewNameError(callExpr.Func.Name, context.String(), context.Class().String())
				return nil, err
			}

			args := []builtins.Value{}
			for _, astArgument := range callExpr.Args {
				arg, err := vm.executeWithContext([]ast.Node{astArgument}, context)
				if err != nil {
					return nil, err
				}

				args = append(args, arg)
			}

			returnValue, returnErr = method.Execute(args...)
		case ast.Assignment:
			assignment := statement.(ast.Assignment)
			returnValue, err := vm.executeWithContext([]ast.Node{assignment.RHS}, context)
			if err != nil {
				return nil, err
			}

			ref, ok := assignment.LHS.(ast.BareReference)
			if !ok {
				panic("unimplemented")
			}

			vm.ObjectSpace[ref.Name] = returnValue

		case ast.FileNameConstReference:
			returnValue = builtins.NewString(vm.currentFilename)
		default:
			panic(fmt.Sprintf("handled unknown statement type: %T:\n\t\n => %#v\n", statement, statement))
		}
	}

	return returnValue, returnErr
}
<commit_msg>VM can interpret global variables<commit_after>package vm

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/grubby/grubby/ast"
	"github.com/grubby/grubby/interpreter/vm/builtins"
	builtin_errors "github.com/grubby/grubby/interpreter/vm/builtins/errors"
	"github.com/grubby/grubby/parser"
)

type vm struct {
	currentFilename string
	ObjectSpace     map[string]builtins.Value
	Globals         map[string]builtins.Value
	KnownSymbols    map[string]builtins.Value
}

type VM interface {
	Run(string) (builtins.Value, error)
	Get(string) (builtins.Value, error)
	MustGet(string) builtins.Value

	Set(string, builtins.Value)

	Symbols() map[string]builtins.Value
}

func NewVM(name string) VM {
	vm := &vm{
		currentFilename: name,
		Globals:         make(map[string]builtins.Value),
		ObjectSpace:     make(map[string]builtins.Value),
		KnownSymbols:    make(map[string]builtins.Value),
	}

	loadPath := builtins.NewArrayClass()
	vm.Globals["$LOAD_PATH"] = loadPath
	vm.Globals["$:"] = loadPath // FIXME: add a test that these are the same object

	objectClass := builtins.NewGlobalObjectClass()
	vm.ObjectSpace["Object"] = objectClass
	vm.ObjectSpace["Kernel"] = builtins.NewGlobalKernelClass()
	vm.ObjectSpace["File"] = builtins.NewFileClass()

	main := objectClass.(builtins.Class).New()
	main.AddMethod(builtins.NewMethod("to_s", func(args ...builtins.Value) (builtins.Value, error) {
		return builtins.NewString("main"), nil
	}))
	main.AddMethod(builtins.NewMethod("require", func(args ...builtins.Value) (builtins.Value, error) {
		fileName := args[0].(*builtins.StringValue).String()
		fileName = fileName[1 : len(fileName)-1]

		for _, pathStr := range loadPath.(*builtins.Array).Members() {
			path := pathStr.(*builtins.StringValue)
			fullPath := filepath.Join(path.String(), fileName+".rb")
			file, err := os.Open(fullPath)
			if err != nil {
				continue
			}

			contents, err := ioutil.ReadAll(file)

			if err == nil {
				originalName := vm.currentFilename
				defer func() {
					vm.currentFilename = originalName
				}()

				vm.currentFilename = file.Name()
				vm.Run(string(contents))
				return nil, nil
			}
		}

		err := fmt.Sprintf("LoadError: cannot load such file -- %s", fileName)
		return nil, builtin_errors.NewLoadError(err)

	}))
	main.AddMethod(builtins.NewMethod("puts", func(args ...builtins.Value) (builtins.Value, error) {
		for _, arg := range args {
			os.Stdout.Write([]byte(arg.String() + "\n"))
		}

		return nil, nil
	}))

	vm.ObjectSpace["main"] = main

	return vm
}

func (vm *vm) MustGet(key string) builtins.Value {
	val, ok := vm.ObjectSpace[key]
	if ok {
		return val
	}

	val, ok = vm.Globals[key]
	if ok {
		return val
	}

	return nil
}

func (vm *vm) Get(key string) (builtins.Value, error) {
	val, ok := vm.ObjectSpace[key]
	if ok {
		return val, nil
	}

	val, ok = vm.Globals[key]
	if ok {
		return val, nil
	}

	return nil, errors.New(fmt.Sprintf("'%s' is undefined", key))
}

func (vm *vm) Set(key string, value builtins.Value) {
	vm.ObjectSpace[key] = value
}

func (vm *vm) Symbols() map[string]builtins.Value {
	return vm.KnownSymbols
}

func (vm *vm) Run(input string) (builtins.Value, error) {
	lexer := parser.NewLexer(input)
	parser.RubyParse(lexer)

	main := vm.ObjectSpace["main"]
	return vm.executeWithContext(parser.Statements, main)
}

func (vm *vm) executeWithContext(statements []ast.Node, context builtins.Value) (builtins.Value, error) {
	var (
		returnValue builtins.Value
		returnErr   error
	)
	for _, statement := range statements {
		switch statement.(type) {
		case ast.FuncDecl:
			// FIXME: assumes for now this will only ever be at the top level
			// it seems like this should be replaced with context, but that's really context for calling methods, not
			// necessarily for defining new methods
			funcNode := statement.(ast.FuncDecl)
			method := builtins.NewMethod(funcNode.Name.Name, func(args ...builtins.Value) (builtins.Value, error) {
				return nil, nil
			})
			returnValue = method
			vm.ObjectSpace["Kernel"].AddPrivateMethod(method)
		case ast.SimpleString:
			returnValue = builtins.NewString(statement.(ast.SimpleString).Value)
		case ast.InterpolatedString:
			returnValue = builtins.NewString(statement.(ast.InterpolatedString).Value)
		case ast.ConstantInt:
			returnValue = builtins.NewInt(statement.(ast.ConstantInt).Value)
		case ast.GlobalVariable:
			returnValue = vm.Globals[statement.(ast.GlobalVariable).Name]
		case ast.ConstantFloat:
			returnValue = builtins.NewFloat(statement.(ast.ConstantFloat).Value)
		case ast.Symbol:
			name := statement.(ast.Symbol).Name
			maybe, ok := vm.KnownSymbols[name]
			if !ok {
				returnValue = builtins.NewSymbol(name)
				vm.KnownSymbols[name] = returnValue
			} else {
				returnValue = maybe
			}
		case ast.BareReference:
			name := statement.(ast.BareReference).Name
			maybe, ok := vm.ObjectSpace[name]
			if ok {
				returnValue = maybe
			} else {
				returnErr = builtin_errors.NewNameError(name, context.String(), context.Class().String())
			}
		case ast.CallExpression:
			var method builtins.Method
			callExpr := statement.(ast.CallExpression)
			method, err := context.Method(callExpr.Func.Name)

			if err != nil {
				err := builtin_errors.NewNameError(callExpr.Func.Name, context.String(), context.Class().String())
				return nil, err
			}

			args := []builtins.Value{}
			for _, astArgument := range callExpr.Args {
				arg, err := vm.executeWithContext([]ast.Node{astArgument}, context)
				if err != nil {
					return nil, err
				}

				args = append(args, arg)
			}

			returnValue, returnErr = method.Execute(args...)
		case ast.Assignment:
			assignment := statement.(ast.Assignment)
			returnValue, err := vm.executeWithContext([]ast.Node{assignment.RHS}, context)
			if err != nil {
				return nil, err
			}

			ref, ok := assignment.LHS.(ast.BareReference)
			if !ok {
				panic("unimplemented")
			}

			vm.ObjectSpace[ref.Name] = returnValue

		case ast.FileNameConstReference:
			returnValue = builtins.NewString(vm.currentFilename)
		default:
			panic(fmt.Sprintf("handled unknown statement type: %T:\n\t\n => %#v\n", statement, statement))
		}
	}

	return returnValue, returnErr
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package iterator provides support for standard Google API iterators.
// See https://github.com/GoogleCloudPlatform/gcloud-golang/wiki/Iterator-Guidelines.
package iterator

import (
	"errors"
	"fmt"
	"reflect"
)

// Done is returned by an iterator's Next method when the iteration is
// complete; when there are no more items to return.
var Done = errors.New("no more items in iterator")

// We don't support mixed calls to Next and NextPage because they play
// with the paging state in incompatible ways.
var errMixed = errors.New("iterator: Next and NextPage called on same iterator")

// PageInfo contains information about an iterator's paging state.
type PageInfo struct {
	// Token is the token used to retrieve the next page of items from the
	// API. You may set Token immediately after creating an iterator to
	// begin iteration at a particular point. If Token is the empty string,
	// the iterator will begin with the first eligible item.
	//
	// The result of setting Token after the first call to Next is undefined.
	//
	// After the underlying API method is called to retrieve a page of items,
	// Token is set to the next-page token in the response.
	Token string

	// MaxSize is the maximum number of items returned by a call to the API.
	// Set MaxSize as a hint to optimize the buffering behavior of the iterator.
	// If zero, the page size is determined by the underlying service.
	//
	// Use Pager to retrieve a page of a specific, exact size.
	MaxSize int

	// The error state of the iterator. Manipulated by PageInfo.next and Pager.
	// This is a latch: it starts as nil, and once set should never change.
	err error

	// If true, no more calls to fetch should be made. Set to true when fetch
	// returns an empty page token. The iterator is Done when this is true AND
	// the buffer is empty.
	atEnd bool

	// Function that fetches a page from the underlying service. It should pass
	// the pageSize and pageToken arguments to the service, fill the buffer
	// with the results from the call, and return the next-page token returned
	// by the service. If the underlying RPC takes an int32 page size, pageSize
	// should be silently truncated.
	fetch func(pageSize int, pageToken string) (nextPageToken string, err error)

	// Function that clears the iterator's buffer, returning any currently buffered items.
	bufLen func() int

	// Function that returns the buffer, after setting the buffer variable to nil.
	takeBuf func() interface{}

	// Set to true on first call to PageInfo.next or Pager.NextPage. Used to check
	// for calls to both Next and NextPage with the same iterator.
	nextCalled, nextPageCalled bool
}

// NewPageInfo exposes internals for iterator implementations.
// It is not a stable interface.
var NewPageInfo = newPageInfo

// If an iterator can support paging, its iterator-creating method should call
// this (via the NewPageInfo variable above).
//
// The fetch, bufLen and takeBuf arguments provide access to the
// iterator's internal slice of buffered items. They behave as described in
// PageInfo, above.
//
// The return value is the PageInfo.next method bound to the returned PageInfo value.
// (Returning it avoids exporting PageInfo.next.)
func newPageInfo(fetch func(int, string) (string, error), bufLen func() int, takeBuf func() interface{}) (*PageInfo, func() error) {
	pi := &PageInfo{
		fetch:   fetch,
		bufLen:  bufLen,
		takeBuf: takeBuf,
	}
	return pi, pi.next
}

// Remaining returns the number of items available before the iterator makes another API call.
func (pi *PageInfo) Remaining() int { return pi.bufLen() }

// next provides support for an iterator's Next function. An iterator's Next
// should return the error returned by next if non-nil; else it can assume
// there is at least one item in its buffer, and it should return that item and
// remove it from the buffer.
func (pi *PageInfo) next() error {
	pi.nextCalled = true
	if pi.err != nil { // Once we get an error, always return it.
		// TODO(jba): fix so users can retry on transient errors? Probably not worth it.
		return pi.err
	}
	if pi.nextPageCalled {
		pi.err = errMixed
		return pi.err
	}
	// Loop until we get some items or reach the end.
	for pi.bufLen() == 0 && !pi.atEnd {
		if err := pi.fill(pi.MaxSize); err != nil {
			pi.err = err
			return pi.err
		}
		if pi.Token == "" {
			pi.atEnd = true
		}
	}
	// Either the buffer is non-empty or pi.atEnd is true (or both).
	if pi.bufLen() == 0 {
		// The buffer is empty and pi.atEnd is true, i.e. the service has no
		// more items.
		pi.err = Done
	}
	return pi.err
}

// Call the service to fill the buffer, using size and pi.Token. Set pi.Token to the
// next-page token returned by the call.
// If fill returns a non-nil error, the buffer will be empty.
func (pi *PageInfo) fill(size int) error {
	tok, err := pi.fetch(size, pi.Token)
	if err != nil {
		pi.takeBuf() // clear the buffer
		return err
	}
	pi.Token = tok
	return nil
}

// Pageable is implemented by iterators that support paging.
type Pageable interface {
	// PageInfo returns paging information associated with the iterator.
	PageInfo() *PageInfo
}

// Pager supports retrieving iterator items a page at a time.
type Pager struct {
	pageInfo *PageInfo
	pageSize int
}

// NewPager returns a pager that uses iter. Calls to its NextPage method will
// obtain exactly pageSize items, unless fewer remain. The pageToken argument
// indicates where to start the iteration. Pass the empty string to start at
// the beginning, or pass a token retrieved from a call to Pager.NextPage.
//
// If you use an iterator with a Pager, you must not call Next on the iterator.
func NewPager(iter Pageable, pageSize int, pageToken string) *Pager {
	p := &Pager{
		pageInfo: iter.PageInfo(),
		pageSize: pageSize,
	}
	p.pageInfo.Token = pageToken
	if pageSize <= 0 {
		p.pageInfo.err = errors.New("iterator: page size must be positive")
	}
	return p
}

// NextPage retrieves a sequence of items from the iterator and appends them
// to slicep, which must be a pointer to a slice of the iterator's item type.
// Exactly p.pageSize items will be appended, unless fewer remain.
//
// The first return value is the page token to use for the next page of items.
// If empty, there are no more pages. Aside from checking for the end of the
// iteration, the returned page token is only needed if the iteration is to be
// resumed a later time, in another context (possibly another process).
//
// The second return value is non-nil if an error occurred. It will never be
// the special iterator sentinel value Done. To recognize the end of the
// iteration, compare nextPageToken to the empty string.
func (p *Pager) NextPage(slicep interface{}) (nextPageToken string, err error) {
	p.pageInfo.nextPageCalled = true
	if p.pageInfo.err != nil {
		return "", p.pageInfo.err
	}
	if p.pageInfo.nextCalled {
		p.pageInfo.err = errMixed
		return "", p.pageInfo.err
	}
	if p.pageInfo.bufLen() > 0 {
		return "", errors.New("must call NextPage with an empty buffer")
	}
	// The buffer must be empty here, so takeBuf is a no-op. We call it just to get
	// the buffer's type.
	wantSliceType := reflect.PtrTo(reflect.ValueOf(p.pageInfo.takeBuf()).Type())
	if slicep == nil {
		return "", errors.New("nil passed to Pager.NextPage")
	}
	vslicep := reflect.ValueOf(slicep)
	if vslicep.Type() != wantSliceType {
		return "", fmt.Errorf("slicep should be of type %s, got %T", wantSliceType, slicep)
	}
	for p.pageInfo.bufLen() < p.pageSize {
		if err := p.pageInfo.fill(p.pageSize - p.pageInfo.bufLen()); err != nil {
			p.pageInfo.err = err
			return "", p.pageInfo.err
		}
		if p.pageInfo.Token == "" {
			break
		}
	}
	e := vslicep.Elem()
	e.Set(reflect.AppendSlice(e, reflect.ValueOf(p.pageInfo.takeBuf())))
	return p.pageInfo.Token, nil
}
<commit_msg>iterator: clarify the behaviour of fetch<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package iterator provides support for standard Google API iterators.
// See https://github.com/GoogleCloudPlatform/gcloud-golang/wiki/Iterator-Guidelines.
package iterator

import (
	"errors"
	"fmt"
	"reflect"
)

// Done is returned by an iterator's Next method when the iteration is
// complete; when there are no more items to return.
var Done = errors.New("no more items in iterator")

// We don't support mixed calls to Next and NextPage because they play
// with the paging state in incompatible ways.
var errMixed = errors.New("iterator: Next and NextPage called on same iterator")

// PageInfo contains information about an iterator's paging state.
type PageInfo struct {
	// Token is the token used to retrieve the next page of items from the
	// API. You may set Token immediately after creating an iterator to
	// begin iteration at a particular point. If Token is the empty string,
	// the iterator will begin with the first eligible item.
	//
	// The result of setting Token after the first call to Next is undefined.
	//
	// After the underlying API method is called to retrieve a page of items,
	// Token is set to the next-page token in the response.
	Token string

	// MaxSize is the maximum number of items returned by a call to the API.
	// Set MaxSize as a hint to optimize the buffering behavior of the iterator.
	// If zero, the page size is determined by the underlying service.
	//
	// Use Pager to retrieve a page of a specific, exact size.
	MaxSize int

	// The error state of the iterator. Manipulated by PageInfo.next and Pager.
	// This is a latch: it starts as nil, and once set should never change.
	err error

	// If true, no more calls to fetch should be made. Set to true when fetch
	// returns an empty page token. The iterator is Done when this is true AND
	// the buffer is empty.
	atEnd bool

	// Function that fetches a page from the underlying service. It should pass
	// the pageSize and pageToken arguments to the service, fill the buffer
	// with the results from the call, and return the next-page token returned
	// by the service. The function must not remove any existing items from the
	// buffer. If the underlying RPC takes an int32 page size, pageSize should
	// be silently truncated.
	fetch func(pageSize int, pageToken string) (nextPageToken string, err error)

	// Function that clears the iterator's buffer, returning any currently buffered items.
	bufLen func() int

	// Function that returns the buffer, after setting the buffer variable to nil.
	takeBuf func() interface{}

	// Set to true on first call to PageInfo.next or Pager.NextPage. Used to check
	// for calls to both Next and NextPage with the same iterator.
	nextCalled, nextPageCalled bool
}

// NewPageInfo exposes internals for iterator implementations.
// It is not a stable interface.
var NewPageInfo = newPageInfo

// If an iterator can support paging, its iterator-creating method should call
// this (via the NewPageInfo variable above).
//
// The fetch, bufLen and takeBuf arguments provide access to the
// iterator's internal slice of buffered items. They behave as described in
// PageInfo, above.
//
// The return value is the PageInfo.next method bound to the returned PageInfo value.
// (Returning it avoids exporting PageInfo.next.)
func newPageInfo(fetch func(int, string) (string, error), bufLen func() int, takeBuf func() interface{}) (*PageInfo, func() error) {
	pi := &PageInfo{
		fetch:   fetch,
		bufLen:  bufLen,
		takeBuf: takeBuf,
	}
	return pi, pi.next
}

// Remaining returns the number of items available before the iterator makes another API call.
func (pi *PageInfo) Remaining() int { return pi.bufLen() }

// next provides support for an iterator's Next function. An iterator's Next
// should return the error returned by next if non-nil; else it can assume
// there is at least one item in its buffer, and it should return that item and
// remove it from the buffer.
func (pi *PageInfo) next() error {
	pi.nextCalled = true
	if pi.err != nil { // Once we get an error, always return it.
		// TODO(jba): fix so users can retry on transient errors? Probably not worth it.
		return pi.err
	}
	if pi.nextPageCalled {
		pi.err = errMixed
		return pi.err
	}
	// Loop until we get some items or reach the end.
	for pi.bufLen() == 0 && !pi.atEnd {
		if err := pi.fill(pi.MaxSize); err != nil {
			pi.err = err
			return pi.err
		}
		if pi.Token == "" {
			pi.atEnd = true
		}
	}
	// Either the buffer is non-empty or pi.atEnd is true (or both).
	if pi.bufLen() == 0 {
		// The buffer is empty and pi.atEnd is true, i.e. the service has no
		// more items.
		pi.err = Done
	}
	return pi.err
}

// Call the service to fill the buffer, using size and pi.Token. Set pi.Token to the
// next-page token returned by the call.
// If fill returns a non-nil error, the buffer will be empty.
func (pi *PageInfo) fill(size int) error {
	tok, err := pi.fetch(size, pi.Token)
	if err != nil {
		pi.takeBuf() // clear the buffer
		return err
	}
	pi.Token = tok
	return nil
}

// Pageable is implemented by iterators that support paging.
type Pageable interface {
	// PageInfo returns paging information associated with the iterator.
	PageInfo() *PageInfo
}

// Pager supports retrieving iterator items a page at a time.
type Pager struct {
	pageInfo *PageInfo
	pageSize int
}

// NewPager returns a pager that uses iter. Calls to its NextPage method will
// obtain exactly pageSize items, unless fewer remain. The pageToken argument
// indicates where to start the iteration. Pass the empty string to start at
// the beginning, or pass a token retrieved from a call to Pager.NextPage.
//
// If you use an iterator with a Pager, you must not call Next on the iterator.
func NewPager(iter Pageable, pageSize int, pageToken string) *Pager {
	p := &Pager{
		pageInfo: iter.PageInfo(),
		pageSize: pageSize,
	}
	p.pageInfo.Token = pageToken
	if pageSize <= 0 {
		p.pageInfo.err = errors.New("iterator: page size must be positive")
	}
	return p
}

// NextPage retrieves a sequence of items from the iterator and appends them
// to slicep, which must be a pointer to a slice of the iterator's item type.
// Exactly p.pageSize items will be appended, unless fewer remain.
//
// The first return value is the page token to use for the next page of items.
// If empty, there are no more pages. Aside from checking for the end of the
// iteration, the returned page token is only needed if the iteration is to be
// resumed a later time, in another context (possibly another process).
//
// The second return value is non-nil if an error occurred. It will never be
// the special iterator sentinel value Done. To recognize the end of the
// iteration, compare nextPageToken to the empty string.
func (p *Pager) NextPage(slicep interface{}) (nextPageToken string, err error) {
	p.pageInfo.nextPageCalled = true
	if p.pageInfo.err != nil {
		return "", p.pageInfo.err
	}
	if p.pageInfo.nextCalled {
		p.pageInfo.err = errMixed
		return "", p.pageInfo.err
	}
	if p.pageInfo.bufLen() > 0 {
		return "", errors.New("must call NextPage with an empty buffer")
	}
	// The buffer must be empty here, so takeBuf is a no-op. We call it just to get
	// the buffer's type.
	wantSliceType := reflect.PtrTo(reflect.ValueOf(p.pageInfo.takeBuf()).Type())
	if slicep == nil {
		return "", errors.New("nil passed to Pager.NextPage")
	}
	vslicep := reflect.ValueOf(slicep)
	if vslicep.Type() != wantSliceType {
		return "", fmt.Errorf("slicep should be of type %s, got %T", wantSliceType, slicep)
	}
	for p.pageInfo.bufLen() < p.pageSize {
		if err := p.pageInfo.fill(p.pageSize - p.pageInfo.bufLen()); err != nil {
			p.pageInfo.err = err
			return "", p.pageInfo.err
		}
		if p.pageInfo.Token == "" {
			break
		}
	}
	e := vslicep.Elem()
	e.Set(reflect.AppendSlice(e, reflect.ValueOf(p.pageInfo.takeBuf())))
	return p.pageInfo.Token, nil
}
<|endoftext|>


<commit_before>package main

import (
	. "bitbucket.org/sinbad/git-lob/Godeps/_workspace/src/github.com/onsi/ginkgo"
	. "bitbucket.org/sinbad/git-lob/Godeps/_workspace/src/github.com/onsi/gomega"
	"bitbucket.org/sinbad/git-lob/providers/smart"
	"bytes"
	"net"
	"os"
	"path/filepath"
	"strings"
)

var _ = Describe("git-lob-serve tests", func() {
	Context("Test individual server requests", func() {

		var config *Config
		var repopath string
		var testchunkdata []byte
		testsha := "5e0865e76e8956900c3ef6fec2d2af1c05f31ec4"
		metacontent := `{"SHA":"5e0865e76e8956900c3ef6fec2d2af1c05f31ec4","Size":21982,"NumChunks":4}`
		testchunkdatasz := int64(16384)
		testchunkidx := 3
		// pickloblist := []string{"1234567890abcdef1234567890abcdef12345678", testsha, "0000000000000000000011111111112222222222"}
		// deltaBaseSHA := "1234567890abcdef1234567890abcdef12345678"
		// deltaTargetSHA := "5e0865e76e8956900c3ef6fec2d2af1c05f31ec4"

		BeforeEach(func() {
			config = NewConfig()
			config.BasePath = filepath.Join(os.TempDir(), "git-lob-serve-test")
			os.MkdirAll(config.BasePath, 0755)
			repopath = "test/repo"

			testchunkdata = make([]byte, testchunkdatasz)
			// put something interesting in it so we can detect it at each end
			testchunkdata[0] = '8'
			testchunkdata[1] = 'p'
			testchunkdata[2] = 'q'
			testchunkdata[3] = 'L'
			testchunkdata[testchunkdatasz-1] = 'z'
			testchunkdata[testchunkdatasz-2] = '2'
			testchunkdata[testchunkdatasz-3] = '5'
		})
		AfterEach(func() {
			os.RemoveAll(config.BasePath)
		})

		It("Queries capabilities (client + reference server)", func() {
			cli, srv := net.Pipe()
			var outerr bytes.Buffer
			// 'Serve' is the real server function, usually connected to stdin/stdout but to pipe for test
			go Serve(srv, srv, &outerr, config, repopath)
			defer cli.Close()

			trans := smart.NewPersistentTransport(cli)
			caps, err := trans.QueryCaps()
			Expect(err).To(BeNil(), "Should be no error")
			Expect(caps).To(ConsistOf([]string{"binary_delta"}))
			Expect(outerr.String()).To(HaveLen(0), "Nothing should be written to stderr")

		})

		It("Uploads & downloads simple files (client + reference server)", func() {
			cli, srv := net.Pipe()
			var outerr bytes.Buffer

			// 'Serve' is the real server function, usually connected to stdin/stdout but to pipe for test
			go Serve(srv, srv, &outerr, config, repopath)
			defer cli.Close()

			trans := smart.NewPersistentTransport(cli)

			// in this test we'll upload totally 'wrong' LOBs but we're just testing plumbing
			exists, err := trans.MetadataExists(testsha)
			Expect(err).To(BeNil(), "Should not be an error in MetadataExists")
			Expect(exists).To(BeFalse(), "Metadata should not exist yet")

			exists, err = trans.ChunkExists(testsha, testchunkidx)
			Expect(err).To(BeNil(), "Should not be an error in ChunkExists")
			Expect(exists).To(BeFalse(), "Chunk should not exist yet")

			// Upload metadata now
			metardr := strings.NewReader(metacontent)
			err = trans.UploadMetadata(testsha, int64(len(metacontent)), metardr)
			Expect(err).To(BeNil(), "Should not be an error in UploadMetadata")
			Expect(metardr.Len()).To(BeZero(), "Server should have read all bytes")
			s, err := os.Stat(getLOBMetaFilePath(testsha, config, repopath))
			Expect(err).To(BeNil(), "Should not be an error stat'ing metadata")
			Expect(s.Size()).To(BeEquivalentTo(len(metacontent)), "Server should have saved metacontent at right size")

			exists, err = trans.MetadataExists(testsha)
			Expect(err).To(BeNil(), "Should not be an error in MetadataExists")
			Expect(exists).To(BeTrue(), "Metadata should now exist")

			// Upload chunk (no callback used, that's tested in client tests)
			callback := func(bytesDone, totalBytes int64) {}
			chunkrdr := bytes.NewReader(testchunkdata)
			err = trans.UploadChunk(testsha, testchunkidx, testchunkdatasz, chunkrdr, callback)
			Expect(err).To(BeNil(), "Should not be an error in UploadChunk")
			Expect(chunkrdr.Len()).To(BeZero(), "Server should have read all bytes")
			s, err = os.Stat(getLOBChunkFilePath(testsha, testchunkidx, config, repopath))
			Expect(err).To(BeNil(), "Should not be an error stat'ing chunk")
			Expect(s.Size()).To(BeEquivalentTo(testchunkdatasz), "Server should have saved chunk at right size")

			exists, err = trans.ChunkExists(testsha, testchunkidx)
			Expect(err).To(BeNil(), "Should not be an error in ChunkExists")
			Expect(exists).To(BeTrue(), "Chunk should now exist")

			exists, err = trans.ChunkExistsAndIsOfSize(testsha, testchunkidx, testchunkdatasz)
			Expect(err).To(BeNil(), "Should not be an error in ChunkExistsAndIsOfSize")
			Expect(exists).To(BeTrue(), "Chunk should now exist & be correct size")

			// Now try to download same data
			var buf bytes.Buffer
			err = trans.DownloadMetadata(testsha, &buf)
			Expect(err).To(BeNil(), "Should not be an error in DownloadMetadata")
			Expect(string(buf.Bytes())).To(Equal(metacontent), "Should download expected metadata content")

			buf.Reset()
			err = trans.DownloadChunk(testsha, testchunkidx, &buf, callback)
			Expect(err).To(BeNil(), "Should not be an error in DownloadChunk")
			Expect(buf.Len()).To(BeEquivalentTo(testchunkdatasz), "Should download the correct number of bytes")
			// Just check start & end of buffers
			contentbytes := buf.Bytes()
			Expect(contentbytes[:20]).To(Equal(testchunkdata[:20]), "Start of downloaded buffer should match")
			Expect(contentbytes[testchunkdatasz-20:]).To(Equal(testchunkdata[testchunkdatasz-20:]), "Start of downloaded buffer should match")

		})

	})

})
<commit_msg>Test server fails safely when asked for incorrect sha to download<commit_after>package main

import (
	. "bitbucket.org/sinbad/git-lob/Godeps/_workspace/src/github.com/onsi/ginkgo"
	. "bitbucket.org/sinbad/git-lob/Godeps/_workspace/src/github.com/onsi/gomega"
	"bitbucket.org/sinbad/git-lob/providers/smart"
	"bytes"
	"net"
	"os"
	"path/filepath"
	"strings"
)

var _ = Describe("git-lob-serve tests", func() {
	Context("Test individual server requests", func() {

		var config *Config
		var repopath string
		var testchunkdata []byte
		testsha := "5e0865e76e8956900c3ef6fec2d2af1c05f31ec4"
		metacontent := `{"SHA":"5e0865e76e8956900c3ef6fec2d2af1c05f31ec4","Size":21982,"NumChunks":4}`
		testchunkdatasz := int64(16384)
		testchunkidx := 3
		// pickloblist := []string{"1234567890abcdef1234567890abcdef12345678", testsha, "0000000000000000000011111111112222222222"}
		// deltaBaseSHA := "1234567890abcdef1234567890abcdef12345678"
		// deltaTargetSHA := "5e0865e76e8956900c3ef6fec2d2af1c05f31ec4"

		BeforeEach(func() {
			config = NewConfig()
			config.BasePath = filepath.Join(os.TempDir(), "git-lob-serve-test")
			os.MkdirAll(config.BasePath, 0755)
			repopath = "test/repo"

			testchunkdata = make([]byte, testchunkdatasz)
			// put something interesting in it so we can detect it at each end
			testchunkdata[0] = '8'
			testchunkdata[1] = 'p'
			testchunkdata[2] = 'q'
			testchunkdata[3] = 'L'
			testchunkdata[testchunkdatasz-1] = 'z'
			testchunkdata[testchunkdatasz-2] = '2'
			testchunkdata[testchunkdatasz-3] = '5'
		})
		AfterEach(func() {
			os.RemoveAll(config.BasePath)
		})

		It("Queries capabilities (client + reference server)", func() {
			cli, srv := net.Pipe()
			var outerr bytes.Buffer
			// 'Serve' is the real server function, usually connected to stdin/stdout but to pipe for test
			go Serve(srv, srv, &outerr, config, repopath)
			defer cli.Close()

			trans := smart.NewPersistentTransport(cli)
			caps, err := trans.QueryCaps()
			Expect(err).To(BeNil(), "Should be no error")
			Expect(caps).To(ConsistOf([]string{"binary_delta"}))
			Expect(outerr.String()).To(HaveLen(0), "Nothing should be written to stderr")

		})

		It("Uploads & downloads simple files (client + reference server)", func() {
			cli, srv := net.Pipe()
			var outerr bytes.Buffer

			// 'Serve' is the real server function, usually connected to stdin/stdout but to pipe for test
			go Serve(srv, srv, &outerr, config, repopath)
			defer cli.Close()

			trans := smart.NewPersistentTransport(cli)

			// in this test we'll upload totally 'wrong' LOBs but we're just testing plumbing
			exists, err := trans.MetadataExists(testsha)
			Expect(err).To(BeNil(), "Should not be an error in MetadataExists")
			Expect(exists).To(BeFalse(), "Metadata should not exist yet")

			exists, err = trans.ChunkExists(testsha, testchunkidx)
			Expect(err).To(BeNil(), "Should not be an error in ChunkExists")
			Expect(exists).To(BeFalse(), "Chunk should not exist yet")

			// Upload metadata now
			metardr := strings.NewReader(metacontent)
			err = trans.UploadMetadata(testsha, int64(len(metacontent)), metardr)
			Expect(err).To(BeNil(), "Should not be an error in UploadMetadata")
			Expect(metardr.Len()).To(BeZero(), "Server should have read all bytes")
			s, err := os.Stat(getLOBMetaFilePath(testsha, config, repopath))
			Expect(err).To(BeNil(), "Should not be an error stat'ing metadata")
			Expect(s.Size()).To(BeEquivalentTo(len(metacontent)), "Server should have saved metacontent at right size")

			exists, err = trans.MetadataExists(testsha)
			Expect(err).To(BeNil(), "Should not be an error in MetadataExists")
			Expect(exists).To(BeTrue(), "Metadata should now exist")

			// Upload chunk (no callback used, that's tested in client tests)
			callback := func(bytesDone, totalBytes int64) {}
			chunkrdr := bytes.NewReader(testchunkdata)
			err = trans.UploadChunk(testsha, testchunkidx, testchunkdatasz, chunkrdr, callback)
			Expect(err).To(BeNil(), "Should not be an error in UploadChunk")
			Expect(chunkrdr.Len()).To(BeZero(), "Server should have read all bytes")
			s, err = os.Stat(getLOBChunkFilePath(testsha, testchunkidx, config, repopath))
			Expect(err).To(BeNil(), "Should not be an error stat'ing chunk")
			Expect(s.Size()).To(BeEquivalentTo(testchunkdatasz), "Server should have saved chunk at right size")

			exists, err = trans.ChunkExists(testsha, testchunkidx)
			Expect(err).To(BeNil(), "Should not be an error in ChunkExists")
			Expect(exists).To(BeTrue(), "Chunk should now exist")

			exists, err = trans.ChunkExistsAndIsOfSize(testsha, testchunkidx, testchunkdatasz)
			Expect(err).To(BeNil(), "Should not be an error in ChunkExistsAndIsOfSize")
			Expect(exists).To(BeTrue(), "Chunk should now exist & be correct size")

			// Now try to download same data
			var buf bytes.Buffer
			err = trans.DownloadMetadata(testsha, &buf)
			Expect(err).To(BeNil(), "Should not be an error in DownloadMetadata")
			Expect(string(buf.Bytes())).To(Equal(metacontent), "Should download expected metadata content")

			buf.Reset()
			err = trans.DownloadChunk(testsha, testchunkidx, &buf, callback)
			Expect(err).To(BeNil(), "Should not be an error in DownloadChunk")
			Expect(buf.Len()).To(BeEquivalentTo(testchunkdatasz), "Should download the correct number of bytes")
			// Just check start & end of buffers
			contentbytes := buf.Bytes()
			Expect(contentbytes[:20]).To(Equal(testchunkdata[:20]), "Start of downloaded buffer should match")
			Expect(contentbytes[testchunkdatasz-20:]).To(Equal(testchunkdata[testchunkdatasz-20:]), "Start of downloaded buffer should match")

			// Make sure it fails safely when asking for the wrong SHA
			buf.Reset()
			err = trans.DownloadMetadata("0000000000000000000000000000000000000000", &buf)
			Expect(err).ToNot(BeNil(), "Should be an error in DownloadMetadata when asking for incorrect sha")
			Expect(buf.Len()).To(BeEquivalentTo(0), "Nothing should have been downloaded")
			err = trans.DownloadChunk("0000000000000000000000000000000000000000", 0, &buf, callback)
			Expect(err).ToNot(BeNil(), "Should be an error in DownloadChunk when asking for incorrect sha")
			Expect(buf.Len()).To(BeEquivalentTo(0), "Nothing should have been downloaded")

		})

	})

})
<|endoftext|>


<commit_before>package appcast

import (
	"encoding/xml"
	"regexp"

	"github.com/victorpopkov/go-appcast/release"
)

// GitHubAtomFeedAppcaster is the interface that wraps the
// GitHubAtomFeedAppcaster methods.
type GitHubAtomFeedAppcaster interface {
	Appcaster
}

// GitHubAtomFeedAppcast represents appcast for "GitHub Atom Feed" that is
// created by GitHub.
type GitHubAtomFeedAppcast struct {
	Appcast
}

// unmarshalGitHubAtomFeed represents an Atom itself.
type unmarshalGitHubAtomFeed struct {
	Entries []unmarshalGitHubAtomFeedEntry `xml:"entry"`
}

// unmarshalGitHubAtomFeedEntry represents an Atom entry.
type unmarshalGitHubAtomFeedEntry struct {
	ID      string `xml:"id"`
	Updated string `xml:"updated"`
	Title   string `xml:"title"`
	Content string `xml:"content"`
}

// UnmarshalReleases unmarshals the Appcast.source.content into the
// Appcast.releases for the "GitHub Atom Feed" provider.
//
// It returns both: the supported provider-specific appcast implementing the
// Appcaster interface and an error.
func (a *GitHubAtomFeedAppcast) UnmarshalReleases() (Appcaster, error) {
	var x unmarshalGitHubAtomFeed

	xml.Unmarshal(a.source.Content(), &x)

	items := make([]release.Releaser, len(x.Entries))
	for i, entry := range x.Entries {
		version := ""

		re := regexp.MustCompile(`\/.*\/(.*$)`)
		if re.MatchString(entry.ID) {
			// extract last part that represents version
			versionMatches := re.FindAllStringSubmatch(entry.ID, 1)
			version = versionMatches[0][1]

			// remove the first "v"
			re := regexp.MustCompile(`^v`)
			version = re.ReplaceAllString(version, "")
		}

		// new release
		r, err := release.New(version, "")
		if err != nil {
			return nil, err
		}

		r.SetTitle(entry.Title)
		r.SetDescription(entry.Content)

		// publishedDateTime
		p := release.NewPublishedDateTime()
		p.Parse(entry.Updated)
		r.SetPublishedDateTime(p)

		// prerelease
		if r.Version().Prerelease() != "" {
			r.SetIsPreRelease(true)
		}

		// add release
		items[i] = r
	}

	a.releases = items

	return a, nil
}
<commit_msg>Return error when no source in the GitHubAtomFeedAppcast.UnmarshalReleases<commit_after>package appcast

import (
	"encoding/xml"
	"fmt"
	"regexp"

	"github.com/victorpopkov/go-appcast/release"
)

// GitHubAtomFeedAppcaster is the interface that wraps the
// GitHubAtomFeedAppcaster methods.
type GitHubAtomFeedAppcaster interface {
	Appcaster
}

// GitHubAtomFeedAppcast represents appcast for "GitHub Atom Feed" that is
// created by GitHub.
type GitHubAtomFeedAppcast struct {
	Appcast
}

// unmarshalGitHubAtomFeed represents an Atom itself.
type unmarshalGitHubAtomFeed struct {
	Entries []unmarshalGitHubAtomFeedEntry `xml:"entry"`
}

// unmarshalGitHubAtomFeedEntry represents an Atom entry.
type unmarshalGitHubAtomFeedEntry struct {
	ID      string `xml:"id"`
	Updated string `xml:"updated"`
	Title   string `xml:"title"`
	Content string `xml:"content"`
}

// UnmarshalReleases unmarshals the Appcast.source.content into the
// Appcast.releases for the "GitHub Atom Feed" provider.
//
// It returns both: the supported provider-specific appcast implementing the
// Appcaster interface and an error.
func (a *GitHubAtomFeedAppcast) UnmarshalReleases() (Appcaster, error) {
	var feed unmarshalGitHubAtomFeed

	if a.source == nil || len(a.source.Content()) == 0 {
		return nil, fmt.Errorf("no source")
	}

	xml.Unmarshal(a.source.Content(), &feed)

	items := make([]release.Releaser, len(feed.Entries))
	for i, entry := range feed.Entries {
		version := ""

		re := regexp.MustCompile(`\/.*\/(.*$)`)
		if re.MatchString(entry.ID) {
			// extract last part that represents version
			versionMatches := re.FindAllStringSubmatch(entry.ID, 1)
			version = versionMatches[0][1]

			// remove the first "v"
			re := regexp.MustCompile(`^v`)
			version = re.ReplaceAllString(version, "")
		}

		// new release
		r, err := release.New(version, "")
		if err != nil {
			return nil, err
		}

		r.SetTitle(entry.Title)
		r.SetDescription(entry.Content)

		// publishedDateTime
		p := release.NewPublishedDateTime()
		p.Parse(entry.Updated)
		r.SetPublishedDateTime(p)

		// prerelease
		if r.Version().Prerelease() != "" {
			r.SetIsPreRelease(true)
		}

		// add release
		items[i] = r
	}

	a.releases = items

	return a, nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"

	log "github.com/Sirupsen/logrus"
)

func runQemu(args []string) {
	qemuFlags := flag.NewFlagSet("qemu", flag.ExitOnError)
	qemuFlags.Usage = func() {
		fmt.Printf("USAGE: %s run qemu [options] prefix\n\n", os.Args[0])
		fmt.Printf("'prefix' specifies the path to the VM image.\n")
		fmt.Printf("\n")
		fmt.Printf("Options:\n")
		qemuFlags.PrintDefaults()
	}

	// Determine Flags
	qemuGUI := qemuFlags.Bool("gui", false, "Set qemu to use video output instead of stdio")
	qemuUEFI := qemuFlags.Bool("uefi", false, "Set UEFI boot from 'prefix'-efi.iso")
	qemuIso := qemuFlags.Bool("iso", false, "Set Legacy BIOS boot from 'prefix'.iso")
	qemuKernel := qemuFlags.Bool("kernel", true, "Set boot using 'prefix'-bzImage/-initrd/-cmdline")

	// Paths and settings for Disks and UEFI firware
	qemuDiskPath := qemuFlags.String("diskpath", "", "Path to disk image to use")
	qemuDiskSize := qemuFlags.String("disksize", "", "Size of disk to create, only created if it doesn't exist")
	qemuFWPath := qemuFlags.String("fwpath", "/usr/share/ovmf/bios.bin", "Path to OVMF firmware for UEFI boot")

	// VM configuration
	qemuArch := qemuFlags.String("arch", "x86_64", "Type of architecture to use, e.g. x86_64, aarch64")
	qemuCPUs := qemuFlags.String("cpus", "1", "Number of CPUs")
	qemuMem := qemuFlags.String("mem", "1024", "Amount of memory in MB")

	if err := qemuFlags.Parse(args); err != nil {
		log.Fatal("Unable to parse args")
	}
	remArgs := qemuFlags.Args()

	if len(remArgs) == 0 {
		fmt.Println("Please specify the prefix to the image to boot")
		qemuFlags.Usage()
		os.Exit(1)
	}
	prefix := remArgs[0]

	// Print warning if conflicting UEFI and ISO flags are set
	if *qemuUEFI && *qemuIso {
		log.Warnf("Both -iso and -uefi have been used")
	}

	// Before building qemu arguments, check qemu is in the $PATH
	qemuBinPath := "qemu-system-" + *qemuArch
	qemuImgPath := "qemu-img"
	fullQemuPath, err := exec.LookPath(qemuBinPath)
	if err != nil {
		log.Fatalf("Unable to find %s within the $PATH", qemuBinPath)
	}

	// Iterate through the flags and build arguments
	var qemuArgs []string
	qemuArgs = append(qemuArgs, "-device", "virtio-rng-pci")
	qemuArgs = append(qemuArgs, "-smp", *qemuCPUs)
	qemuArgs = append(qemuArgs, "-m", *qemuMem)

	// Look for kvm device and enable for qemu if it exists
	if _, err = os.Stat("/dev/kvm"); os.IsNotExist(err) {
		qemuArgs = append(qemuArgs, "-machine", "q35")
	} else {
		qemuArgs = append(qemuArgs, "-enable-kvm")
		qemuArgs = append(qemuArgs, "-machine", "q35,accel=kvm:tcg")
	}

	if *qemuDiskPath != "" {
		// If disk doesn't exist then create one
		if _, err = os.Stat(*qemuDiskPath); os.IsNotExist(err) {
			log.Infof("Creating new qemu disk [%s]", *qemuDiskPath)
			fullQemuImgPath, err := exec.LookPath(qemuImgPath)
			if err != nil {
				log.Fatalf("Unable to find %s within the $PATH", qemuImgPath)
			}
			cmd := exec.Command(fullQemuImgPath, "create", "-f", "qcow2", *qemuDiskPath, *qemuDiskSize)
			if err = cmd.Run(); err != nil {
				log.Fatalf("Error creating disk [%s]:  %s", *qemuDiskPath, err.Error())
			}
		} else {
			log.Infof("Using existing disk [%s]", *qemuDiskPath)
		}
		qemuArgs = append(qemuArgs, "-drive", "file="+*qemuDiskPath+",format=qcow2")
	}

	// Check flags for iso/uefi boot and if so disable kernel boot
	if *qemuIso {
		*qemuKernel = false
		qemuIsoPath := buildPath(prefix, ".iso")
		qemuArgs = append(qemuArgs, "-cdrom", qemuIsoPath)
	}

	if *qemuUEFI {
		// Check for OVMF firmware before building paths
		_, err = os.Stat(*qemuFWPath)
		if err != nil {
			if os.IsNotExist(err) {
				log.Fatalf("File [%s] does not exist, please ensure OVMF is installed", *qemuFWPath)
			} else {
				log.Fatalf("%s", err.Error())
			}
		}

		*qemuKernel = false
		qemuIsoPath := buildPath(prefix, "-efi.iso")
		qemuArgs = append(qemuArgs, "-pflash", *qemuFWPath)
		qemuArgs = append(qemuArgs, "-cdrom", qemuIsoPath)
		qemuArgs = append(qemuArgs, "-boot", "d")
	}

	// build kernel boot config from bzImage/initrd/cmdline
	if *qemuKernel {
		qemuKernelPath := buildPath(prefix, "-bzImage")
		qemuInitrdPath := buildPath(prefix, "-initrd.img")
		qemuArgs = append(qemuArgs, "-kernel", qemuKernelPath)
		qemuArgs = append(qemuArgs, "-initrd", qemuInitrdPath)
		consoleString, err := ioutil.ReadFile(prefix + "-cmdline")
		if err != nil {
			log.Infof(" %s\n defaulting to console output", err.Error())
			qemuArgs = append(qemuArgs, "-append", "console=ttyS0 console=tty0 page_poison=1")
		} else {
			qemuArgs = append(qemuArgs, "-append", string(consoleString))
		}
	}

	if *qemuGUI != true {
		qemuArgs = append(qemuArgs, "-nographic")
	}

	// If verbosity is enabled print out the full path/arguments
	log.Debugf("%s %v\n", fullQemuPath, qemuArgs)

	cmd := exec.Command(fullQemuPath, qemuArgs...)

	// If we're not using a seperate window then link the execution to stdin/out
	if *qemuGUI != true {
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
	}

	if err = cmd.Run(); err != nil {
		log.Fatalf("Error starting %s: %s", fullQemuPath, err.Error())
	}
}

func buildPath(prefix string, postfix string) string {
	path := prefix + postfix
	if _, err := os.Stat(path); os.IsNotExist(err) {
		log.Fatalf("File [%s] does not exist in current directory", path)
	}
	return path
}
<commit_msg>moby: Add container fallback for moby run qemu<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"

	log "github.com/Sirupsen/logrus"
)

// QemuImg is the version of qemu container
const QemuImg = "linuxkit/qemu:17f052263d63c8a2b641ad91c589edcbb8a18c82"

// QemuConfig contains the config for Qemu
type QemuConfig struct {
	Prefix        string
	ISO           bool
	UEFI          bool
	Kernel        bool
	GUI           bool
	DiskPath      string
	DiskSize      string
	FWPath        string
	Arch          string
	CPUs          string
	Memory        string
	KVM           bool
	Containerized bool
	QemuBinPath   string
	QemuImgPath   string
}

func runQemu(args []string) {
	qemuFlags := flag.NewFlagSet("qemu", flag.ExitOnError)
	qemuFlags.Usage = func() {
		fmt.Printf("USAGE: %s run qemu [options] prefix\n\n", os.Args[0])
		fmt.Printf("'prefix' specifies the path to the VM image.\n")
		fmt.Printf("\n")
		fmt.Printf("Options:\n")
		qemuFlags.PrintDefaults()
	}

	// Determine Flags
	qemuGUI := qemuFlags.Bool("gui", false, "Set qemu to use video output instead of stdio")
	qemuUEFI := qemuFlags.Bool("uefi", false, "Set UEFI boot from 'prefix'-efi.iso")
	qemuIso := qemuFlags.Bool("iso", false, "Set Legacy BIOS boot from 'prefix'.iso")
	qemuKernel := qemuFlags.Bool("kernel", true, "Set boot using 'prefix'-bzImage/-initrd/-cmdline")

	// Paths and settings for Disks and UEFI firware
	qemuDiskPath := qemuFlags.String("diskpath", "", "Path to disk image to use")
	qemuDiskSize := qemuFlags.String("disksize", "", "Size of disk to create, only created if it doesn't exist")
	qemuFWPath := qemuFlags.String("fwpath", "/usr/share/ovmf/bios.bin", "Path to OVMF firmware for UEFI boot")

	// VM configuration
	qemuArch := qemuFlags.String("arch", "x86_64", "Type of architecture to use, e.g. x86_64, aarch64")
	qemuCPUs := qemuFlags.String("cpus", "1", "Number of CPUs")
	qemuMem := qemuFlags.String("mem", "1024", "Amount of memory in MB")

	if err := qemuFlags.Parse(args); err != nil {
		log.Fatal("Unable to parse args")
	}
	remArgs := qemuFlags.Args()

	if len(remArgs) == 0 {
		fmt.Println("Please specify the prefix to the image to boot")
		qemuFlags.Usage()
		os.Exit(1)
	}
	prefix := remArgs[0]

	// Print warning if conflicting UEFI and ISO flags are set
	if *qemuUEFI && *qemuIso {
		log.Warnf("Both -iso and -uefi have been used")
	}

	config := QemuConfig{
		Prefix:   prefix,
		ISO:      *qemuIso,
		UEFI:     *qemuUEFI,
		Kernel:   *qemuKernel,
		GUI:      *qemuGUI,
		DiskPath: *qemuDiskPath,
		DiskSize: *qemuDiskSize,
		FWPath:   *qemuFWPath,
		Arch:     *qemuArch,
		CPUs:     *qemuCPUs,
		Memory:   *qemuMem,
	}

	config, qemuArgs := buildQemuCmdline(config)

	var err error
	if config.Containerized {
		err = runQemuContainer(config, qemuArgs)
	} else {
		err = runQemuLocal(config, qemuArgs)
	}
	if err != nil {
		log.Fatal(err.Error())
	}
}

func runQemuLocal(config QemuConfig, args []string) error {
	if config.DiskPath != "" {
		// If disk doesn't exist then create one
		if _, err := os.Stat(config.DiskPath); err != nil {
			if os.IsNotExist(err) {
				log.Infof("Creating new qemu disk [%s]", config.DiskPath)
				qemuImgCmd := exec.Command(config.QemuImgPath, "create", "-f", "qcow2", config.DiskPath, config.DiskSize)
				log.Debugf("%v\n", qemuImgCmd.Args)
				if err := qemuImgCmd.Run(); err != nil {
					return fmt.Errorf("Error creating disk [%s]:  %s", config.DiskPath, err.Error())
				}
			} else {
				return err
			}
		} else {
			log.Infof("Using existing disk [%s]", config.DiskPath)
		}
	}

	// Check for OVMF firmware before running
	if config.UEFI {
		if _, err := os.Stat(config.FWPath); err != nil {
			if os.IsNotExist(err) {
				return fmt.Errorf("File [%s] does not exist, please ensure OVMF is installed", config.FWPath)
			}
			return err
		}
	}

	qemuCmd := exec.Command(config.QemuBinPath, args...)
	// If verbosity is enabled print out the full path/arguments
	log.Debugf("%v\n", qemuCmd.Args)

	// If we're not using a separate window then link the execution to stdin/out
	if config.GUI != true {
		qemuCmd.Stdin = os.Stdin
		qemuCmd.Stdout = os.Stdout
		qemuCmd.Stderr = os.Stderr
	}

	return qemuCmd.Run()
}

func runQemuContainer(config QemuConfig, args []string) error {
	wd, err := os.Getwd()
	if err != nil {
		return err
	}

	dockerArgs := []string{"run", "-i", "--rm", "-v", fmt.Sprintf("%s:%s", wd, "/tmp"), "-w", "/tmp"}

	if config.KVM {
		dockerArgs = append(dockerArgs, "--device", "/dev/kvm")
	}

	dockerPath, err := exec.LookPath("docker")
	if err != nil {
		return fmt.Errorf("Unable to find docker in the $PATH")
	}

	if config.DiskPath != "" {
		// If disk doesn't exist then create one
		if _, err = os.Stat(config.DiskPath); err != nil {
			if os.IsNotExist(err) {
				log.Infof("Creating new qemu disk [%s]", config.DiskPath)
				imgArgs := append(dockerArgs, QemuImg, "qemu-img", "create", "-f", "qcow2", config.DiskPath, config.DiskSize)
				qemuImgCmd := exec.Command(dockerPath, imgArgs...)
				log.Debugf("%v\n", qemuImgCmd.Args)
				if err = qemuImgCmd.Run(); err != nil {
					return fmt.Errorf("Error creating disk [%s]:  %s", config.DiskPath, err.Error())
				}
			} else {
				return err
			}
		} else {
			log.Infof("Using existing disk [%s]", config.DiskPath)
		}
	}

	qemuArgs := append(dockerArgs, QemuImg, "qemu-system-"+config.Arch)
	qemuArgs = append(qemuArgs, args...)
	qemuCmd := exec.Command(dockerPath, qemuArgs...)
	// If verbosity is enabled print out the full path/arguments
	log.Debugf("%v\n", qemuCmd.Args)

	// GUI mode not currently supported in a container. Although it could be in future.
	if config.GUI == true {
		return fmt.Errorf("GUI mode is only supported when running locally, not in a container")
	}

	qemuCmd.Stdin = os.Stdin
	qemuCmd.Stdout = os.Stdout
	qemuCmd.Stderr = os.Stderr

	return qemuCmd.Run()
}

func buildQemuCmdline(config QemuConfig) (QemuConfig, []string) {
	// Before building qemu arguments, check if qemu is in the PATH or fallback to containerized
	qemuBinPath := "qemu-system-" + config.Arch
	qemuImgPath := "qemu-img"

	var err error
	config.QemuBinPath, err = exec.LookPath(qemuBinPath)
	if err != nil {
		log.Infof("Unable to find %s within the $PATH. Using a container", qemuBinPath)
		config.Containerized = true
	}

	config.QemuImgPath, err = exec.LookPath(qemuImgPath)
	if err != nil {
		// No need to show the error message twice
		if !config.Containerized {
			log.Infof("Unable to find %s within the $PATH. Using a container", qemuImgPath)
			config.Containerized = true
		}
	}

	// Iterate through the flags and build arguments
	var qemuArgs []string
	qemuArgs = append(qemuArgs, "-device", "virtio-rng-pci")
	qemuArgs = append(qemuArgs, "-smp", config.CPUs)
	qemuArgs = append(qemuArgs, "-m", config.Memory)

	// Look for kvm device and enable for qemu if it exists
	if _, err = os.Stat("/dev/kvm"); os.IsNotExist(err) {
		qemuArgs = append(qemuArgs, "-machine", "q35")
	} else {
		config.KVM = true
		qemuArgs = append(qemuArgs, "-enable-kvm")
		qemuArgs = append(qemuArgs, "-machine", "q35,accel=kvm:tcg")
	}

	if config.DiskPath != "" {
		qemuArgs = append(qemuArgs, "-drive", "file="+config.DiskPath+",format=qcow2")
	}

	// Check flags for iso/uefi boot and if so disable kernel boot
	if config.ISO {
		config.Kernel = false
		qemuIsoPath := buildPath(config.Prefix, ".iso")
		qemuArgs = append(qemuArgs, "-cdrom", qemuIsoPath)
	}

	if config.UEFI {
		config.Kernel = false
		qemuIsoPath := buildPath(config.Prefix, "-efi.iso")
		qemuArgs = append(qemuArgs, "-pflash", config.FWPath)
		qemuArgs = append(qemuArgs, "-cdrom", qemuIsoPath)
		qemuArgs = append(qemuArgs, "-boot", "d")
	}

	// build kernel boot config from bzImage/initrd/cmdline
	if config.Kernel {
		qemuKernelPath := buildPath(config.Prefix, "-bzImage")
		qemuInitrdPath := buildPath(config.Prefix, "-initrd.img")
		qemuArgs = append(qemuArgs, "-kernel", qemuKernelPath)
		qemuArgs = append(qemuArgs, "-initrd", qemuInitrdPath)
		consoleString, err := ioutil.ReadFile(config.Prefix + "-cmdline")
		if err != nil {
			log.Infof(" %s\n defaulting to console output", err.Error())
			qemuArgs = append(qemuArgs, "-append", "console=ttyS0 console=tty0 page_poison=1")
		} else {
			qemuArgs = append(qemuArgs, "-append", string(consoleString))
		}
	}

	if config.GUI != true {
		qemuArgs = append(qemuArgs, "-nographic")
	}

	return config, qemuArgs
}

func buildPath(prefix string, postfix string) string {
	path := prefix + postfix
	if _, err := os.Stat(path); os.IsNotExist(err) {
		log.Fatalf("File [%s] does not exist in current directory", path)
	}
	return path
}
<|endoftext|>


<commit_before><commit_msg>upgrade static js<commit_after><|endoftext|>