<commit_before>package device

import (
	"crypto/sha256"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"
	"github.com/xshellinc/go-virtualbox"
	"github.com/xshellinc/iotit/device/config"
	"github.com/xshellinc/iotit/repo"
	"github.com/xshellinc/iotit/vbox"
	"github.com/xshellinc/tools/dialogs"
	"github.com/xshellinc/tools/lib/help"
	"gopkg.in/urfave/cli.v1"
)

// Flasher is an entity for flashing different devices
type Flasher interface {
	Flash() error
	Configure() error
	Write() error
}

// flasher contains virtualbox machine, ssh connection, repository, currently selected device and image name
type flasher struct {
	Quiet   bool
	vbox    *virtualbox.Machine
	conf    *vbox.Config
	devRepo *repo.DeviceMapping
	CLI     *cli.Context

	img     string
	folder  string
	device  string
	mounted bool
}

var retries = 0

// validates given image path and downloads image archive to os tmp folder
func (d *flasher) DownloadImage() (fileName, filePath string, err error) {
	wg := &sync.WaitGroup{}
	if !help.ValidURL(d.devRepo.Image.URL) {
		// local file path given
		filePath = d.devRepo.Image.URL
		fileName = filepath.Base(filePath)
		log.WithField("path", filePath).WithField("name", fileName).Info("Custom image")
		if !help.Exists(filePath) {
			fmt.Println("[-] Image location is neither valid URL nor existing file. Aborting.")
			return "", "", errors.New("Invalid image location")
		}
		fmt.Println("[+] Using local image file for ", d.device)
		return fileName, filePath, err
	}
	// download image over http
	fmt.Println("[+] Starting download", d.devRepo.Image.Title)
	log.WithField("url", d.devRepo.Image.URL).WithField("dir", d.devRepo.Dir()).Debug("download")
	name, bar, err := help.DownloadFromUrlWithAttemptsAsync(d.devRepo.Image.URL, d.devRepo.Dir(), 3, wg)
	if err != nil {
		return "", "", err
	}
	fileName = name
	filePath = filepath.Join(d.devRepo.Dir(), fileName)

	bar.Prefix(fmt.Sprintf("[+] Download %-15s", fileName))
	bar.Start()
	wg.Wait()
	bar.Finish()
	time.Sleep(time.Second * 2)
	return fileName, filePath, err
}

// Prepare method inits virtualbox, downloads os image and uploads it into vm
func (d *flasher) Prepare() error {
	log.Debug("Prepare")
	if err := vbox.CheckVBInstalled(); err != nil {
		return err
	}

	d.conf = vbox.NewConfig(d.device)
	log.Debug("Configuring virtual box")
	var err error
	if d.vbox, err = d.conf.GetVbox(d.device, d.Quiet); err != nil {
		return err
	}
	log.WithField("name", d.vbox.Name).Info("Selected profile")

	if d.vbox.State != virtualbox.Running {
		fmt.Printf(`[+] Using virtual machine
	Name - `+dialogs.PrintColored("%s")+`
	Description - `+dialogs.PrintColored("%s")+"\n", d.vbox.Name, d.vbox.Description)

		if err := d.startVM(); err != nil {
			return err
		}
	}

	help.DeleteHost(filepath.Join(help.UserHomeDir(), ".ssh", "known_hosts"), "localhost")

	return d.prepareImage()
}

func (d *flasher) startVM() error {
	job := help.NewBackgroundJob()
	if err := d.vbox.Start(); err != nil {
		return err
	}

	go func() {
		ticker := time.NewTicker(15 * time.Second)
		defer ticker.Stop()
		defer job.Close()
		for {
			select {
			case <-ticker.C:
				_, eut, err := d.conf.SSH.Run("whoami")
				if err == nil && strings.TrimSpace(eut) == "" {
					return
				}
			case <-time.After(180 * time.Second):
				job.Error(errors.New("Cannot connect to vbox via ssh"))
			}
		}
	}()

	if err := help.WaitJobAndSpin("Starting", job); err != nil {
		return err
	}

	time.Sleep(time.Second)
	return nil
}

func (d *flasher) prepareImage() error {
	var (
		fileName, filePath string
	)

	if fn, fp, err := d.DownloadImage(); err == nil {
		fileName = fn
		filePath = fp
	} else {
		return err
	}

	if len(d.devRepo.Image.Hash) > 0 {
		fmt.Println("[+] Calculating sha256 of the downloaded image")
		f, err := os.Open(filePath)
		if err != nil {
			return err
		}
		defer f.Close()

		h := sha256.New()
		if _, err := io.Copy(h, f); err != nil {
			return err
		}
		hash := fmt.Sprintf("%x", h.Sum(nil))
		log.WithField("sum", hash).WithField("hash", d.devRepo.Image.Hash).Debug("comparing")
		if hash != d.devRepo.Image.Hash {
			return fmt.Errorf("wrong sha256 hash")
		}
		fmt.Println("[+] SHA256 verified")
	}

	if err := d.uploadImage(fileName, filePath); err != nil {
		return err
	}

	if d.img == "" {
		if err := d.extractImage(fileName); err != nil {
			return err
		}
	}

	return nil
}

func (d *flasher) uploadImage(fileName, filePath string) error {
	if _, eut, err := d.conf.SSH.Run("ls " + config.TmpDir + fileName); err != nil || len(strings.TrimSpace(eut)) > 0 {
		fmt.Printf("[+] Uploading %s to virtual machine\n", fileName)
		if err := d.conf.SSH.Scp(filePath, config.TmpDir); err != nil {
			return err
		}
	}
	log.Debug("Image already exists inside VM")
	log.WithField("path", filePath).WithField("name", fileName).Info("Image")
	return nil
}

func (d *flasher) extractImage(fileName string) error {
	if strings.HasSuffix(fileName, ".img") {
		d.img = fileName
		return nil
	}

	if strings.HasSuffix(fileName, ".zip") {
		if files, err := help.GetZipFiles(help.AddPathSuffix(runtime.GOOS, d.devRepo.Dir(), fileName)); err == nil && len(files) == 1 {
			if _, eut, err := d.conf.SSH.Run("ls " + config.TmpDir + files[0].Name); err == nil && len(strings.TrimSpace(eut)) == 0 {
				log.Debug("Image file already extracted")
				d.img = files[0].Name
			}
		}
	}
	d.conf.SSH.Run("rm -rf " + config.TmpDir + "*.img")
	fmt.Printf("[+] Extracting %s \n", fileName)
	command := fmt.Sprintf(help.GetExtractCommand(fileName), help.AddPathSuffix("unix", config.TmpDir, fileName), config.TmpDir)
	log.WithField("command", command).Debug("Extracting an image...")
	d.conf.SSH.SetTimer(help.SshExtendedCommandTimeout)
	out, eut, err := d.conf.SSH.Run(command)
	out = strings.TrimSpace(out)
	eut = strings.TrimSpace(eut)
	if err != nil || len(eut) > 0 {
		log.WithField("err", eut).Debug("Extract error")
		if eut == "unzip: crc error" || strings.Contains(eut, "unzip: corrupted data") {
			filePath := filepath.Join(d.devRepo.Dir(), fileName)
			if help.Exists(filePath) && retries < 2 {
				fmt.Println("[-] CRC error, re-trying...")
				log.WithField("filePath", filePath).Info("Trying to download and extract image again")
				help.DeleteFile(filePath)
				d.conf.SSH.Run("rm " + config.TmpDir + fileName)
				retries++
				return d.prepareImage()
			}
		}
		fmt.Println("[-] ", eut)
		return err
	}
	log.Debug(out)
	for _, raw := range strings.Split(out, " ") {
		s := strings.TrimSpace(raw)
		if s != "" && strings.HasSuffix(s, ".img") {
			d.img = s
			return nil
		}
	}
	if out, _, err := d.conf.SSH.Run("ls -1 " + config.TmpDir); err == nil && len(strings.TrimSpace(out)) > 0 {
		log.WithField("command", "ls -1").Debug(out)
		for _, raw := range strings.Split(strings.TrimSpace(out), "\n") {
			s := strings.TrimSpace(raw)
			if d.folder == "" {
				d.folder = s
			}
			if s != "" && strings.HasSuffix(s, ".img") {
				d.img = s
				return nil
			}
		}
	}
	return nil
}

// Configure is a generic mock method
func (d *flasher) Configure() error {
	fmt.Println("Mock, nothing to configure")
	return nil
}

// Write is a generic method
func (d *flasher) Write() error {
	fmt.Println("Mock, nothing to write")
	return nil
}

// Flash configures and flashes image
func (d *flasher) Flash() error {
	fmt.Println("Mock, nothing to flash")
	return nil
}

// Done prints out final success message
func (d *flasher) Done() error {
	if err := d.conf.Stop(d.Quiet); err != nil {
		log.Error(err)
	}
	fmt.Println("\t\t ...                      .................    ..                ")
	fmt.Println("\t\t ...                      .................   ....    ...        ")
	fmt.Println("\t\t ...                             ....                 ...        ")
	fmt.Println("\t\t ...          .....              ....                 ...        ")
	fmt.Println("\t\t ...       ...........           ....         ...     .......... ")
	fmt.Println("\t\t ...      ...       ...          ....         ...     ...        ")
	fmt.Println("\t\t ...     ...         ...         ....         ...     ...        ")
	fmt.Println("\t\t ...     ...         ...         ....         ...     ...        ")
	fmt.Println("\t\t ...     ...         ...         ....         ...     ...        ")
	fmt.Println("\t\t ...     ....       ....         ....         ...      ...       ")
	fmt.Println("\t\t ...      .....   .....          ....         ...      ....   .. ")
	fmt.Println("\t\t ...         .......             ....         ...        ....... ")
	fmt.Println("\n\t\t Flashing Complete!")
	fmt.Println("\t\t If you have any questions or suggestions feel free to make an issue at https://github.com/xshellinc/iotit/issues/ or tweet us @isaax_iot")
	return nil
}
<commit_msg>Add ability to extract zip image on a host in case of error<commit_after>package device

import (
	"crypto/sha256"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"
	"github.com/xshellinc/go-virtualbox"
	"github.com/xshellinc/iotit/device/config"
	"github.com/xshellinc/iotit/repo"
	"github.com/xshellinc/iotit/vbox"
	"github.com/xshellinc/tools/dialogs"
	"github.com/xshellinc/tools/lib/help"
	"gopkg.in/urfave/cli.v1"
)

// Flasher is an entity for flashing different devices
type Flasher interface {
	Flash() error
	Configure() error
	Write() error
}

// flasher contains virtualbox machine, ssh connection, repository, currently selected device and image name
type flasher struct {
	Quiet   bool
	vbox    *virtualbox.Machine
	conf    *vbox.Config
	devRepo *repo.DeviceMapping
	CLI     *cli.Context

	img     string
	folder  string
	device  string
	mounted bool
}

var retries = 0

// validates given image path and downloads image archive to os tmp folder
func (d *flasher) DownloadImage() (fileName, filePath string, err error) {
	wg := &sync.WaitGroup{}
	if !help.ValidURL(d.devRepo.Image.URL) {
		// local file path given
		filePath = d.devRepo.Image.URL
		fileName = filepath.Base(filePath)
		log.WithField("path", filePath).WithField("name", fileName).Info("Custom image")
		if !help.Exists(filePath) {
			fmt.Println("[-] Image location is neither valid URL nor existing file. Aborting.")
			return "", "", errors.New("Invalid image location")
		}
		fmt.Println("[+] Using local image file for ", d.device)
		return fileName, filePath, err
	}
	// download image over http
	fmt.Println("[+] Starting download", d.devRepo.Image.Title)
	log.WithField("url", d.devRepo.Image.URL).WithField("dir", d.devRepo.Dir()).Debug("download")
	name, bar, err := help.DownloadFromUrlWithAttemptsAsync(d.devRepo.Image.URL, d.devRepo.Dir(), 3, wg)
	if err != nil {
		return "", "", err
	}
	fileName = name
	filePath = filepath.Join(d.devRepo.Dir(), fileName)

	bar.Prefix(fmt.Sprintf("[+] Download %-15s", fileName))
	bar.Start()
	wg.Wait()
	bar.Finish()
	time.Sleep(time.Second * 2)
	return fileName, filePath, err
}

// Prepare method inits virtualbox, downloads os image and uploads it into vm
func (d *flasher) Prepare() error {
	log.Debug("Prepare")
	if err := vbox.CheckVBInstalled(); err != nil {
		return err
	}

	d.conf = vbox.NewConfig(d.device)
	log.Debug("Configuring virtual box")
	var err error
	if d.vbox, err = d.conf.GetVbox(d.device, d.Quiet); err != nil {
		return err
	}
	log.WithField("name", d.vbox.Name).Info("Selected profile")

	if d.vbox.State != virtualbox.Running {
		fmt.Printf(`[+] Using virtual machine
	Name - `+dialogs.PrintColored("%s")+`
	Description - `+dialogs.PrintColored("%s")+"\n", d.vbox.Name, d.vbox.Description)

		if err := d.startVM(); err != nil {
			return err
		}
	}

	help.DeleteHost(filepath.Join(help.UserHomeDir(), ".ssh", "known_hosts"), "localhost")

	return d.prepareImage()
}

func (d *flasher) startVM() error {
	job := help.NewBackgroundJob()
	if err := d.vbox.Start(); err != nil {
		return err
	}

	go func() {
		ticker := time.NewTicker(15 * time.Second)
		defer ticker.Stop()
		defer job.Close()
		for {
			select {
			case <-ticker.C:
				_, eut, err := d.conf.SSH.Run("whoami")
				if err == nil && strings.TrimSpace(eut) == "" {
					return
				}
			case <-time.After(180 * time.Second):
				job.Error(errors.New("Cannot connect to vbox via ssh"))
			}
		}
	}()

	if err := help.WaitJobAndSpin("Starting", job); err != nil {
		return err
	}

	time.Sleep(time.Second)
	return nil
}

func (d *flasher) prepareImage() error {
	var (
		fileName, filePath string
	)

	if fn, fp, err := d.DownloadImage(); err == nil {
		fileName = fn
		filePath = fp
	} else {
		return err
	}

	if len(d.devRepo.Image.Hash) > 0 {
		fmt.Println("[+] Calculating SHA256 of the downloaded image")
		f, err := os.Open(filePath)
		if err != nil {
			return err
		}
		defer f.Close()

		h := sha256.New()
		if _, err := io.Copy(h, f); err != nil {
			return err
		}
		hash := fmt.Sprintf("%x", h.Sum(nil))
		log.WithField("sum", hash).WithField("hash", d.devRepo.Image.Hash).Debug("comparing")
		if hash != d.devRepo.Image.Hash {
			return fmt.Errorf("wrong SHA256 hash")
		}
		fmt.Println("[+] SHA256 verified")
	}

	if err := d.uploadImage(fileName, filePath); err != nil {
		return err
	}

	if d.img == "" {
		if err := d.extractImage(fileName, filePath); err != nil {
			return err
		}
	}

	return nil
}

func (d *flasher) uploadImage(fileName, filePath string) error {
	if _, eut, err := d.conf.SSH.Run("ls " + config.TmpDir + fileName); err != nil || len(strings.TrimSpace(eut)) > 0 {
		fmt.Printf("[+] Uploading %s to virtual machine\n", fileName)
		if err := d.conf.SSH.Scp(filePath, config.TmpDir); err != nil {
			return err
		}
	}
	log.Debug("Image already exists inside VM")
	log.WithField("path", filePath).WithField("name", fileName).Info("Image")
	return nil
}

func (d *flasher) extractImage(fileName, localPath string) error {
	if strings.HasSuffix(fileName, ".img") {
		d.img = fileName
		return nil
	}

	if strings.HasSuffix(fileName, ".zip") {
		if files, err := help.GetZipFiles(help.AddPathSuffix(runtime.GOOS, d.devRepo.Dir(), fileName)); err == nil && len(files) == 1 {
			if _, eut, err := d.conf.SSH.Run("ls " + config.TmpDir + files[0].Name); err == nil && len(strings.TrimSpace(eut)) == 0 {
				log.Debug("Image file already extracted")
				d.img = files[0].Name
			}
		}
	}

	d.conf.SSH.Run("rm -rf " + config.TmpDir + "*.img")
	fmt.Printf("[+] Extracting %s \n", fileName)
	command := fmt.Sprintf(help.GetExtractCommand(fileName), help.AddPathSuffix("unix", config.TmpDir, fileName), config.TmpDir)

	log.WithField("command", command).Debug("Extracting an image...")
	d.conf.SSH.SetTimer(help.SshExtendedCommandTimeout)
	out, eut, err := d.conf.SSH.Run(command)
	out = strings.TrimSpace(out)
	eut = strings.TrimSpace(eut)

	if err != nil || len(eut) > 0 {
		log.WithField("out", out).WithField("eut", eut).WithField("err", err).Debug("Extract error")
		if eut == "unzip: crc error" || strings.Contains(eut, "unzip: corrupted data") {
			filePath := filepath.Join(d.devRepo.Dir(), fileName)
			if help.Exists(filePath) && retries < 2 {
				fmt.Println("[-] CRC error, re-trying...")
				log.WithField("filePath", filePath).Info("Trying to download and extract image again")
				help.DeleteFile(filePath)
				d.conf.SSH.Run("rm " + config.TmpDir + fileName)
				retries++
				return d.prepareImage()
			}
		} else if strings.Contains(eut, "unzip: bad length") {
			// unzip inside VM failed, trying to unzip on a host and upload full image
			log.WithField("p", localPath).Debug("unzip into: ", help.GetTempDir())
			if err := help.Unzip(localPath, help.GetTempDir()); err != nil {
				return err
			}
			files, err := filepath.Glob("*")
			if err != nil {
				return err
			}
			fileName = ""
			for _, f := range files {
				if strings.HasSuffix(f, ".img") {
					fileName = f
				}
			}
			if err := d.uploadImage(fileName, filepath.Join(help.GetTempDir(), fileName)); err != nil {
				return err
			}
			log.Debug("uploaded")
		} else {
			fmt.Println("[-] ", eut)
			return fmt.Errorf(eut)
		}
	} else {
		log.Debug(out)

		for _, raw := range strings.Split(out, " ") {
			s := strings.TrimSpace(raw)
			if s != "" && strings.HasSuffix(s, ".img") {
				d.img = s
				return nil
			}
		}
	}

	if out, _, err := d.conf.SSH.Run("ls -1 " + config.TmpDir); err == nil && len(strings.TrimSpace(out)) > 0 {
		log.WithField("command", "ls -1").Debug(out)
		for _, raw := range strings.Split(strings.TrimSpace(out), "\n") {
			s := strings.TrimSpace(raw)
			if d.folder == "" {
				d.folder = s
			}
			if s != "" && strings.HasSuffix(s, ".img") {
				d.img = s
				return nil
			}
		}
	}
	return nil
}

// Configure is a generic mock method
func (d *flasher) Configure() error {
	fmt.Println("Mock, nothing to configure")
	return nil
}

// Write is a generic method
func (d *flasher) Write() error {
	fmt.Println("Mock, nothing to write")
	return nil
}

// Flash configures and flashes image
func (d *flasher) Flash() error {
	fmt.Println("Mock, nothing to flash")
	return nil
}

// Done prints out final success message
func (d *flasher) Done() error {
	if err := d.conf.Stop(d.Quiet); err != nil {
		log.Error(err)
	}
	fmt.Println("\t\t ...                      .................    ..                ")
	fmt.Println("\t\t ...                      .................   ....    ...        ")
	fmt.Println("\t\t ...                             ....                 ...        ")
	fmt.Println("\t\t ...          .....              ....                 ...        ")
	fmt.Println("\t\t ...       ...........           ....         ...     .......... ")
	fmt.Println("\t\t ...      ...       ...          ....         ...     ...        ")
	fmt.Println("\t\t ...     ...         ...         ....         ...     ...        ")
	fmt.Println("\t\t ...     ...         ...         ....         ...     ...        ")
	fmt.Println("\t\t ...     ...         ...         ....         ...     ...        ")
	fmt.Println("\t\t ...     ....       ....         ....         ...      ...       ")
	fmt.Println("\t\t ...      .....   .....          ....         ...      ....   .. ")
	fmt.Println("\t\t ...         .......             ....         ...        ....... ")
	fmt.Println("\n\t\t Flashing Complete!")
	fmt.Println("\t\t If you have any questions or suggestions feel free to make an issue at https://github.com/xshellinc/iotit/issues/ or tweet us @isaax_iot")
	return nil
}
<|endoftext|>


<commit_before>package device

import (
	"github.com/Comcast/webpa-common/xmetrics"
	"github.com/go-kit/kit/metrics"
	"github.com/go-kit/kit/metrics/provider"
)

const (
	DeviceCounter             = "device_count"
	DuplicatesCounter         = "duplicate_count"
	RequestResponseCounter    = "request_response_count"
	PingCounter               = "ping_count"
	PongCounter               = "pong_count"
	ConnectCounter            = "connect_count"
	DisconnectCounter         = "disconnect_count"
	DeviceLimitReachedCounter = "device_limit_reached_count"
)

// Metrics is the device module function that adds default device metrics
func Metrics() []xmetrics.Metric {
	return []xmetrics.Metric{
		xmetrics.Metric{
			Name: DeviceCounter,
			Type: "gauge",
		},
		xmetrics.Metric{
			Name: DuplicatesCounter,
			Type: "counter",
		},
		xmetrics.Metric{
			Name: RequestResponseCounter,
			Type: "counter",
		},
		xmetrics.Metric{
			Name: PingCounter,
			Type: "counter",
		},
		xmetrics.Metric{
			Name: PongCounter,
			Type: "counter",
		},
		xmetrics.Metric{
			Name: ConnectCounter,
			Type: "counter",
		},
		xmetrics.Metric{
			Name: DisconnectCounter,
			Type: "counter",
		},
		xmetrics.Metric{
			Name: DeviceLimitReachedCounter,
			Type: "counter",
		},
	}
}

// Measures is a convenient struct that holds all the device-related metric objects for runtime consumption.
type Measures struct {
	Device          xmetrics.Setter
	LimitReached    xmetrics.Incrementer
	Duplicates      xmetrics.Incrementer
	RequestResponse metrics.Counter
	Ping            xmetrics.Incrementer
	Pong            xmetrics.Incrementer
	Connect         xmetrics.Incrementer
	Disconnect      xmetrics.Adder
}

// NewMeasures constructs a Measures given a go-kit metrics Provider
func NewMeasures(p provider.Provider) Measures {
	return Measures{
		Device:          p.NewGauge(DeviceCounter),
		LimitReached:    xmetrics.NewIncrementer(p.NewCounter(DeviceLimitReachedCounter)),
		RequestResponse: p.NewCounter(RequestResponseCounter),
		Ping:            xmetrics.NewIncrementer(p.NewCounter(PingCounter)),
		Pong:            xmetrics.NewIncrementer(p.NewCounter(PongCounter)),
		Duplicates:      xmetrics.NewIncrementer(p.NewCounter(DuplicatesCounter)),
		Connect:         xmetrics.NewIncrementer(p.NewCounter(ConnectCounter)),
		Disconnect:      p.NewCounter(DisconnectCounter),
	}
}
<commit_msg>simplified metrics declaration<commit_after>package device

import (
	"github.com/Comcast/webpa-common/xmetrics"
	"github.com/go-kit/kit/metrics"
	"github.com/go-kit/kit/metrics/provider"
)

const (
	DeviceCounter             = "device_count"
	DuplicatesCounter         = "duplicate_count"
	RequestResponseCounter    = "request_response_count"
	PingCounter               = "ping_count"
	PongCounter               = "pong_count"
	ConnectCounter            = "connect_count"
	DisconnectCounter         = "disconnect_count"
	DeviceLimitReachedCounter = "device_limit_reached_count"
)

// Metrics is the device module function that adds default device metrics
func Metrics() []xmetrics.Metric {
	return []xmetrics.Metric{
		{
			Name: DeviceCounter,
			Type: "gauge",
		},
		{
			Name: DuplicatesCounter,
			Type: "counter",
		},
		{
			Name: RequestResponseCounter,
			Type: "counter",
		},
		{
			Name: PingCounter,
			Type: "counter",
		},
		{
			Name: PongCounter,
			Type: "counter",
		},
		{
			Name: ConnectCounter,
			Type: "counter",
		},
		{
			Name: DisconnectCounter,
			Type: "counter",
		},
		{
			Name: DeviceLimitReachedCounter,
			Type: "counter",
		},
	}
}

// Measures is a convenient struct that holds all the device-related metric objects for runtime consumption.
type Measures struct {
	Device          xmetrics.Setter
	LimitReached    xmetrics.Incrementer
	Duplicates      xmetrics.Incrementer
	RequestResponse metrics.Counter
	Ping            xmetrics.Incrementer
	Pong            xmetrics.Incrementer
	Connect         xmetrics.Incrementer
	Disconnect      xmetrics.Adder
}

// NewMeasures constructs a Measures given a go-kit metrics Provider
func NewMeasures(p provider.Provider) Measures {
	return Measures{
		Device:          p.NewGauge(DeviceCounter),
		LimitReached:    xmetrics.NewIncrementer(p.NewCounter(DeviceLimitReachedCounter)),
		RequestResponse: p.NewCounter(RequestResponseCounter),
		Ping:            xmetrics.NewIncrementer(p.NewCounter(PingCounter)),
		Pong:            xmetrics.NewIncrementer(p.NewCounter(PongCounter)),
		Duplicates:      xmetrics.NewIncrementer(p.NewCounter(DuplicatesCounter)),
		Connect:         xmetrics.NewIncrementer(p.NewCounter(ConnectCounter)),
		Disconnect:      p.NewCounter(DisconnectCounter),
	}
}
<|endoftext|>


<commit_before>package asciidocgo

import (
	"strings"
	"unicode/utf8"
)

type Documentable interface {
	Document() Documentable
	Attributes() map[string]interface{}
	Attr(name string, defaultValue interface{}, inherit bool) interface{}
	HasAttr(name string, expect interface{}, inherit bool) bool
	setAttr(name string, val interface{}, override bool) bool
	HasReftext() bool

	Safe() safeMode
	BaseDir() string
}

/* An abstract base class that provides state and methods for managing
a node of AsciiDoc content.
The state and methods on this class are comment to all content segments
in an AsciiDoc document. */
type abstractNode struct {
	parent     Documentable
	context    context
	document   Documentable
	attributes map[string]interface{}
	*substitutors
}

func newAbstractNode(parent Documentable, context context) *abstractNode {
	abstractNode := &abstractNode{parent, context, nil, make(map[string]interface{}), &substitutors{}}
	if context == document {
		abstractNode.parent = nil
		abstractNode.document = parent
	} else if parent != nil {
		abstractNode.document = parent.Document()
	}
	return abstractNode
}

//  Get the element which is the parent of this node
func (an *abstractNode) Parent() Documentable {
	return an.parent
}

//  Get the Asciidoctor::Document to which this node belongs
func (an *abstractNode) Document() Documentable {
	return an.document
}

// Get the Symbol context for this node
func (an *abstractNode) Context() context {
	return an.context
}

func (an *abstractNode) Attributes() map[string]interface{} {
	return an.attributes
}

// Associate this Block with a new parent Block
// parent: The Block to set as the parent of this Block
func (an *abstractNode) SetParent(parent *abstractNode) {
	an.parent = parent
	if parent != nil {
		an.document = parent.Document()
	} else {
		an.document = nil
	}
}

/* Get the value for the specified attribute.

First look in the attributes on this node and return the value
of the attribute if found.
Otherwise, if this node is a child of the Document node, look in
the attributes of the Document node and return the value of the attribute
if found.
Otherwise, return the default value, which defaults to nil.

- name: the String or Symbol name of the attribute to lookup
- default_value: the Object value to return if the attribute is not found
(default: nil)
- inherit: a Boolean indicating whether to check for the attribute on
the AsciiDoctor::Document if not found on this node (default: false)

Return the value of the attribute or the default value if the attribute is
not found in the attributes of this node or the document node
*/
func (an *abstractNode) Attr(name string, defaultValue interface{}, inherit bool) interface{} {
	if an == an.document {
		inherit = false
	}
	if an.attributes[name] != nil {
		return an.attributes[name]
	}
	if inherit {
		if an.document != nil && an.document.Attributes()[name] != nil {
			return an.document.Attributes()[name]
		}
	}
	return defaultValue
}

/*
Check if the attribute is defined, optionally performing a comparison of
its value if expected is not nil

Check if the attribute is defined.
First look in the attributes on this node.
If not found, and this node is a child of the Document node,
look in the attributes of the Document node.
If the attribute is found and a comparison value is specified (not nil),
return whether the two values match.
Otherwise, return whether the attribute was found.

name    - the String or Symbol name of the attribute to lookup
expect  - the expected Object value of the attribute (default: nil)
inherit - a Boolean indicating whether to check for the attribute on the
          AsciiDoctor::Document if not found on this node (default: false)

return a Boolean indicating whether the attribute exists and, if a
comparison value is specified, whether the value of the attribute matches
the comparison value
*/
func (an *abstractNode) HasAttr(name string, expect interface{}, inherit bool) bool {
	if an == an.document {
		inherit = false
	}
	if expect == nil {
		if _, hasAttr := an.attributes[name]; hasAttr {
			return true
		}
		if inherit {
			if an.document != nil {
				if _, hasAttr := an.document.Attributes()[name]; hasAttr {
					return true
				}
			}
		}
		return false
	}
	if an.attributes[name] != nil {
		return (expect == an.attributes[name])
	}
	if inherit {
		if an.document != nil && an.document.Attributes()[name] != nil {
			return (expect == an.document.Attributes()[name])
		}
	}
	return false
}

/* Assign the value to the specified key in this block's attributes hash.

- key: The attribute key (or name)
- val: The value to assign to the key

returns a flag indicating whether the assignment was performed
*/
func (an *abstractNode) setAttr(name string, val interface{}, override bool) bool {
	if override {
		an.attributes[name] = val
		return true
	}
	if _, hasName := an.attributes[name]; !hasName {
		an.attributes[name] = val
		return true
	}
	return false
}

/* Enable a specified option attribute on the current node.

This method defines the `%name%-option` attribute on the current node.

- name: the String or Symbol name of the option
*/
func (an *abstractNode) SetOption(option string) {
	val := an.attributes["options"]
	if val == nil {
		valmap := make(map[string]bool)
		an.attributes["options"] = valmap
		val = valmap
	}
	valmap := val.(map[string]bool)
	if _, hasOption := valmap[option]; !hasOption {
		valmap[option] = true
	}
	an.attributes[option+"-option"] = true
}

/*  A convenience method to check if the specified option attribute is enabled
on the current node.

Check if the option is enabled.
This method simply checks to see if the `%name%-option` attribute is defined
on the current node.

- name: the String or Symbol name of the option

return a Boolean indicating whether the option has been specified
*/
func (an *abstractNode) HasOption(option string) bool {
	_, res := an.attributes[option+"-option"]
	return res
}

/* Update the attributes of this node with the new values
in the attributes argument.

If an attribute already exists with the same key,
it's value will be overridden.

- attributes: A Hash of attributes to assign to this node.
*/
func (an *abstractNode) Update(attrs map[string]interface{}) {
	for key, value := range attrs {
		an.attributes[key] = value
	}
}

// A convenience method that checks if the specified role is present
// in the list of roles on this node
func (an *abstractNode) HasRole(role interface{}) bool {
	if role == nil {
		if _, hasRole := an.attributes["role"]; hasRole {
			return true
		}
		if an.Document() != nil {
			if _, hasRole := an.Document().Attributes()["role"]; hasRole {
				return true
			}
		}
		return false
	}
	if anAttr := an.Attr("role", nil, true); anAttr == role {
		return true
	}
	return false
}

// A convenience method that checks if the specified role is present
// in the list of roles on this node
func (an *abstractNode) HasARole(name string) bool {
	if name == "" {
		return false
	}
	// inherit = true: check an.Document() as well
	roles := an.Attr("role", nil, true)
	if roles == nil {
		return false
	}
	rolesString := roles.(string)
	rolesArray := strings.Split(rolesString, " ")
	for _, role := range rolesArray {
		if name == role {
			return true
		}
	}
	return false
}

// A convenience method that returns the value of the role attribute
func (an *abstractNode) Role() interface{} {
	// inherit = true: check an.Document() as well
	return an.Attr("role", nil, true)
}

// A convenience method that returns the role names as an Array
func (an *abstractNode) RoleNames() []string {
	roles := an.Attr("role", nil, true)
	if roles == nil {
		return []string{}
	}
	rolesString := roles.(string)
	return strings.Split(rolesString, " ")
}

// A convenience method that checks if the reftext attribute is specified
func (an *abstractNode) HasReftext() bool {
	reftext := an.Attr("reftext", nil, true)
	return (reftext != nil)
}

// A convenience method that returns the value of the reftext attribute
func (an *abstractNode) Reftext() interface{} {
	// inherit = true: check an.Document() as well
	return an.Attr("reftext", nil, true)
}

// Returns a forward slash if the attribute htmlsyntax has the value "xml".
func (an *abstractNode) ShortTagSlash() *rune {
	if an.Document() == nil {
		return nil
	}
	if an.Document().Attr("htmlsyntax", nil, false) == "xml" {
		r, _ := utf8.DecodeLastRuneInString("/")
		return &r
	} else {
		return nil
	}
}

/* Construct a reference or data URI to an icon image
for the specified icon name.

If the 'icon' attribute is set on this block, the name is ignored
and the value of this attribute is used as the  target image path.
Otherwise, construct a target image path by concatenating the value
of the 'iconsdir' attribute, the icon name and the value of the
'icontype' attribute (defaulting to 'png').

The target image path is then passed through the #image_uri() method.
If the 'data-uri' attribute is set on the document, the image will be
safely converted to a data URI.

The return value of this method can be safely used in an image tag.
Returns A String reference or data URI for an icon image */

func (an *abstractNode) IconUri(name string) string {
	if an.HasAttr("icon", nil, false) {
		return ""
		//an.ImageUri(an.Attr("icon", nil, false).(string), "")
	} else {
		return ""
		//an.ImageUri(name+an.Attr("icontype", "png", false), "iconsdir")
	}
}

/* Construct a URI reference to the target media.

If the target media is a URI reference, then leave it untouched.

The target media is resolved relative to the directory retrieved from
the specified attribute key, if provided.

The return value can be safely used in a media tag (img, audio, video).

target        - A String reference to the target media
asset_dir_key - The String attribute key used to lookup the directory where
(default: 'imagesdir')

Returns A String reference for the target media
*/
func (an *abstractNode) MediaUri(target string, assetDirKey string) string {
	if assetDirKey == "" {
		assetDirKey = "imagesdir"
	}
	if strings.Contains(target, ":") && REGEXP[":uri_sniff"].MatchString(target) {
		return target
	} else if assetDirKey != "" && an.HasAttr(assetDirKey, nil, true) {
		// normalize_web_path(target, @document.attr(asset_dir_key)) ???
		// How? (BUG?) @document can be nil.
		// At least, ask attr on an, with inherit true.
		return normalizeWebPath(target, an.Attr(assetDirKey, nil, true).(string))
	}
	return normalizeWebPath(target, "")
}

/* Construct a URI reference or data URI to the target image.

If the target image is a URI reference, then leave it untouched.

The target image is resolved relative to the directory retrieved from the
specified attribute key, if provided.

If the 'data-uri' attribute is set on the document, and the safe mode level
is less than SafeMode::SECURE, the image will be safely converted to
a data URI by reading it from the same directory. If neither of these conditions
are satisfied, a relative path (i.e., URL) will be returned.

The return value of this method can be safely used in an image tag.

target_image - A String path to the target image
asset_dir_key - The String attribute key used to lookup the directory where
the image is located (default: 'imagesdir')

Returns A String reference or data URI for the target image */
func (an *abstractNode) ImageUri(targetImage, assetDirKey string) string {
	if assetDirKey == "" {
		assetDirKey = "imagesdir"
	}
	if strings.Contains(targetImage, ":") && REGEXP[":uri_sniff"].MatchString(targetImage) {
		return targetImage
	}
	// if @document.safe < Asciidoctor::SafeMode::SECURE && @document.attr?('data-uri')
	// if an.Document().HasAttr("data-uri", nil, false)
	if an.Document() != nil && an.Document().Safe() < SECURE && an.Document().HasAttr("data-uri", nil, true) {
		// generate_data_uri(target_image, asset_dir_key)
		return ""
	}
	if assetDirKey != "" && an.HasAttr(assetDirKey, nil, true) {
		return normalizeWebPath(targetImage, an.Document().Attr(assetDirKey, nil, false).(string))
	} else {
		return normalizeWebPath(targetImage, "")
	}
}

/* Generate a data URI that can be used to embed an image in the output document

First, and foremost, the target image path is cleaned if the document
safe mode level is set to at least SafeMode::SAFE
(a condition which is true by default) to prevent access to ancestor paths
in the filesystem.
The image data is then read and converted to Base64.
Finally, a data URI is built which can be used in an image tag.

target_image - A String path to the target image
asset_dir_key - The String attribute key used to lookup the directory where
                the image is located (default: nil)

Returns A String data URI containing the content of the target image*/
func (an *abstractNode) generateDataUri(target_image, asset_dir_key string) string {
	return ""
}

/* Normalize the web page using the PathResolver.
target - the String target path
start  - the String start (i.e, parent) path (optional, default: nil)
returns the resolved String path */
func normalizeWebPath(target, start string) string {
	res := WebPath(target, start)
	return res
}

/* An actual document would have a default safe level of SERVER */
func (an *abstractNode) Safe() safeMode {
	return UNSAFE
}

/* An Actual document would have a base dir */
func (an *abstractNode) BaseDir() string {
	return ""
}
<commit_msg>Start implementing generateDataUri(), which points to normalize_system_path().<commit_after>package asciidocgo

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"unicode/utf8"
)

type Documentable interface {
	Document() Documentable
	Attributes() map[string]interface{}
	Attr(name string, defaultValue interface{}, inherit bool) interface{}
	HasAttr(name string, expect interface{}, inherit bool) bool
	setAttr(name string, val interface{}, override bool) bool
	HasReftext() bool

	Safe() safeMode
	BaseDir() string
}

/* An abstract base class that provides state and methods for managing
a node of AsciiDoc content.
The state and methods on this class are comment to all content segments
in an AsciiDoc document. */
type abstractNode struct {
	parent     Documentable
	context    context
	document   Documentable
	attributes map[string]interface{}
	*substitutors
}

func newAbstractNode(parent Documentable, context context) *abstractNode {
	abstractNode := &abstractNode{parent, context, nil, make(map[string]interface{}), &substitutors{}}
	if context == document {
		abstractNode.parent = nil
		abstractNode.document = parent
	} else if parent != nil {
		abstractNode.document = parent.Document()
	}
	return abstractNode
}

//  Get the element which is the parent of this node
func (an *abstractNode) Parent() Documentable {
	return an.parent
}

//  Get the Asciidoctor::Document to which this node belongs
func (an *abstractNode) Document() Documentable {
	return an.document
}

// Get the Symbol context for this node
func (an *abstractNode) Context() context {
	return an.context
}

func (an *abstractNode) Attributes() map[string]interface{} {
	return an.attributes
}

// Associate this Block with a new parent Block
// parent: The Block to set as the parent of this Block
func (an *abstractNode) SetParent(parent *abstractNode) {
	an.parent = parent
	if parent != nil {
		an.document = parent.Document()
	} else {
		an.document = nil
	}
}

/* Get the value for the specified attribute.

First look in the attributes on this node and return the value
of the attribute if found.
Otherwise, if this node is a child of the Document node, look in
the attributes of the Document node and return the value of the attribute
if found.
Otherwise, return the default value, which defaults to nil.

- name: the String or Symbol name of the attribute to lookup
- default_value: the Object value to return if the attribute is not found
(default: nil)
- inherit: a Boolean indicating whether to check for the attribute on
the AsciiDoctor::Document if not found on this node (default: false)

Return the value of the attribute or the default value if the attribute is
not found in the attributes of this node or the document node
*/
func (an *abstractNode) Attr(name string, defaultValue interface{}, inherit bool) interface{} {
	if an == an.document {
		inherit = false
	}
	if an.attributes[name] != nil {
		return an.attributes[name]
	}
	if inherit {
		if an.document != nil && an.document.Attributes()[name] != nil {
			return an.document.Attributes()[name]
		}
	}
	return defaultValue
}

/*
Check if the attribute is defined, optionally performing a comparison of
its value if expected is not nil

Check if the attribute is defined.
First look in the attributes on this node.
If not found, and this node is a child of the Document node,
look in the attributes of the Document node.
If the attribute is found and a comparison value is specified (not nil),
return whether the two values match.
Otherwise, return whether the attribute was found.

name    - the String or Symbol name of the attribute to lookup
expect  - the expected Object value of the attribute (default: nil)
inherit - a Boolean indicating whether to check for the attribute on the
          AsciiDoctor::Document if not found on this node (default: false)

return a Boolean indicating whether the attribute exists and, if a
comparison value is specified, whether the value of the attribute matches
the comparison value
*/
func (an *abstractNode) HasAttr(name string, expect interface{}, inherit bool) bool {
	if an == an.document {
		inherit = false
	}
	if expect == nil {
		if _, hasAttr := an.attributes[name]; hasAttr {
			return true
		}
		if inherit {
			if an.document != nil {
				if _, hasAttr := an.document.Attributes()[name]; hasAttr {
					return true
				}
			}
		}
		return false
	}
	if an.attributes[name] != nil {
		return (expect == an.attributes[name])
	}
	if inherit {
		if an.document != nil && an.document.Attributes()[name] != nil {
			return (expect == an.document.Attributes()[name])
		}
	}
	return false
}

/* Assign the value to the specified key in this block's attributes hash.

- key: The attribute key (or name)
- val: The value to assign to the key

returns a flag indicating whether the assignment was performed
*/
func (an *abstractNode) setAttr(name string, val interface{}, override bool) bool {
	if override {
		an.attributes[name] = val
		return true
	}
	if _, hasName := an.attributes[name]; !hasName {
		an.attributes[name] = val
		return true
	}
	return false
}

/* Enable a specified option attribute on the current node.

This method defines the `%name%-option` attribute on the current node.

- name: the String or Symbol name of the option
*/
func (an *abstractNode) SetOption(option string) {
	val := an.attributes["options"]
	if val == nil {
		valmap := make(map[string]bool)
		an.attributes["options"] = valmap
		val = valmap
	}
	valmap := val.(map[string]bool)
	if _, hasOption := valmap[option]; !hasOption {
		valmap[option] = true
	}
	an.attributes[option+"-option"] = true
}

/*  A convenience method to check if the specified option attribute is enabled
on the current node.

Check if the option is enabled.
This method simply checks to see if the `%name%-option` attribute is defined
on the current node.

- name: the String or Symbol name of the option

return a Boolean indicating whether the option has been specified
*/
func (an *abstractNode) HasOption(option string) bool {
	_, res := an.attributes[option+"-option"]
	return res
}

/* Update the attributes of this node with the new values
in the attributes argument.

If an attribute already exists with the same key,
it's value will be overridden.

- attributes: A Hash of attributes to assign to this node.
*/
func (an *abstractNode) Update(attrs map[string]interface{}) {
	for key, value := range attrs {
		an.attributes[key] = value
	}
}

// A convenience method that checks if the specified role is present
// in the list of roles on this node
func (an *abstractNode) HasRole(role interface{}) bool {
	if role == nil {
		if _, hasRole := an.attributes["role"]; hasRole {
			return true
		}
		if an.Document() != nil {
			if _, hasRole := an.Document().Attributes()["role"]; hasRole {
				return true
			}
		}
		return false
	}
	if anAttr := an.Attr("role", nil, true); anAttr == role {
		return true
	}
	return false
}

// A convenience method that checks if the specified role is present
// in the list of roles on this node
func (an *abstractNode) HasARole(name string) bool {
	if name == "" {
		return false
	}
	// inherit = true: check an.Document() as well
	roles := an.Attr("role", nil, true)
	if roles == nil {
		return false
	}
	rolesString := roles.(string)
	rolesArray := strings.Split(rolesString, " ")
	for _, role := range rolesArray {
		if name == role {
			return true
		}
	}
	return false
}

// A convenience method that returns the value of the role attribute
func (an *abstractNode) Role() interface{} {
	// inherit = true: check an.Document() as well
	return an.Attr("role", nil, true)
}

// A convenience method that returns the role names as an Array
func (an *abstractNode) RoleNames() []string {
	roles := an.Attr("role", nil, true)
	if roles == nil {
		return []string{}
	}
	rolesString := roles.(string)
	return strings.Split(rolesString, " ")
}

// A convenience method that checks if the reftext attribute is specified
func (an *abstractNode) HasReftext() bool {
	reftext := an.Attr("reftext", nil, true)
	return (reftext != nil)
}

// A convenience method that returns the value of the reftext attribute
func (an *abstractNode) Reftext() interface{} {
	// inherit = true: check an.Document() as well
	return an.Attr("reftext", nil, true)
}

// Returns a forward slash if the attribute htmlsyntax has the value "xml".
func (an *abstractNode) ShortTagSlash() *rune {
	if an.Document() == nil {
		return nil
	}
	if an.Document().Attr("htmlsyntax", nil, false) == "xml" {
		r, _ := utf8.DecodeLastRuneInString("/")
		return &r
	} else {
		return nil
	}
}

/* Construct a reference or data URI to an icon image
for the specified icon name.

If the 'icon' attribute is set on this block, the name is ignored
and the value of this attribute is used as the  target image path.
Otherwise, construct a target image path by concatenating the value
of the 'iconsdir' attribute, the icon name and the value of the
'icontype' attribute (defaulting to 'png').

The target image path is then passed through the #image_uri() method.
If the 'data-uri' attribute is set on the document, the image will be
safely converted to a data URI.

The return value of this method can be safely used in an image tag.
Returns A String reference or data URI for an icon image */

func (an *abstractNode) IconUri(name string) string {
	if an.HasAttr("icon", nil, false) {
		return ""
		//TODO: an.ImageUri(an.Attr("icon", nil, false).(string), "")
	} else {
		return ""
		//TODO: an.ImageUri(name+an.Attr("icontype", "png", false), "iconsdir")
	}
}

/* Construct a URI reference to the target media.

If the target media is a URI reference, then leave it untouched.

The target media is resolved relative to the directory retrieved from
the specified attribute key, if provided.

The return value can be safely used in a media tag (img, audio, video).

target        - A String reference to the target media
asset_dir_key - The String attribute key used to lookup the directory where
(default: 'imagesdir')

Returns A String reference for the target media
*/
func (an *abstractNode) MediaUri(target string, assetDirKey string) string {
	if assetDirKey == "" {
		assetDirKey = "imagesdir"
	}
	if strings.Contains(target, ":") && REGEXP[":uri_sniff"].MatchString(target) {
		return target
	} else if assetDirKey != "" && an.HasAttr(assetDirKey, nil, true) {
		// normalize_web_path(target, @document.attr(asset_dir_key)) ???
		// How? (BUG?) @document can be nil.
		// At least, ask attr on an, with inherit true.
		return normalizeWebPath(target, an.Attr(assetDirKey, nil, true).(string))
	}
	return normalizeWebPath(target, "")
}

/* Construct a URI reference or data URI to the target image.

If the target image is a URI reference, then leave it untouched.

The target image is resolved relative to the directory retrieved from the
specified attribute key, if provided.

If the 'data-uri' attribute is set on the document, and the safe mode level
is less than SafeMode::SECURE, the image will be safely converted to
a data URI by reading it from the same directory. If neither of these conditions
are satisfied, a relative path (i.e., URL) will be returned.

The return value of this method can be safely used in an image tag.

target_image - A String path to the target image
asset_dir_key - The String attribute key used to lookup the directory where
the image is located (default: 'imagesdir')

Returns A String reference or data URI for the target image */
func (an *abstractNode) ImageUri(targetImage, assetDirKey string) string {
	if assetDirKey == "" {
		assetDirKey = "imagesdir"
	}
	if strings.Contains(targetImage, ":") && REGEXP[":uri_sniff"].MatchString(targetImage) {
		return targetImage
	}
	// if @document.safe < Asciidoctor::SafeMode::SECURE && @document.attr?('data-uri')
	// if an.Document().HasAttr("data-uri", nil, false)
	if an.Document() != nil && an.Document().Safe() < SECURE && an.Document().HasAttr("data-uri", nil, true) {
		// TODO: generate_data_uri(target_image, asset_dir_key)
		return ""
	}
	if assetDirKey != "" && an.HasAttr(assetDirKey, nil, true) {
		return normalizeWebPath(targetImage, an.Document().Attr(assetDirKey, nil, false).(string))
	} else {
		return normalizeWebPath(targetImage, "")
	}
}

/* Generate a data URI that can be used to embed an image in the output document

First, and foremost, the target image path is cleaned if the document
safe mode level is set to at least SafeMode::SAFE
(a condition which is true by default) to prevent access to ancestor paths
in the filesystem.
The image data is then read and converted to Base64.
Finally, a data URI is built which can be used in an image tag.

target_image - A String path to the target image
asset_dir_key - The String attribute key used to lookup the directory where
                the image is located (default: nil)

Returns A String data URI containing the content of the target image*/
func (an *abstractNode) generateDataUri(targetImage, assetDirKey string) string {
	ext := filepath.Ext(targetImage)
	mimetype := "image/" + ext
	if ext == "svg" {
		mimetype = mimetype + "+xml"
	}
	imagePath := ""
	if assetDirKey != "" {
		imagePath = ""
		// TODO: image_path = normalize_system_path(target_image, @document.attr(asset_dir_key), nil, :target_name => 'image')
	} else {
		imagePath = ""
		// TODO: image_path = normalize_system_path(target_image)
	}
	if _, err := os.Open(imagePath); err != nil {
		fmt.Errorf("asciidocgo: WARNING: image to embed not found or not readable: '%v'", imagePath)
		return "data:" + mimetype + ":base64,"
		// uncomment to return 1 pixel white dot instead
		// return 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
	}
	if Test == "test_generateDataUri_imagePath" {
		return fmt.Sprintf("imagePath='%v'", imagePath)
	}
	return ""
}

/* Normalize the web page using the PathResolver.
target - the String target path
start  - the String start (i.e, parent) path (optional, default: nil)
returns the resolved String path */
func normalizeWebPath(target, start string) string {
	res := WebPath(target, start)
	return res
}

/* An actual document would have a default safe level of SERVER */
func (an *abstractNode) Safe() safeMode {
	return UNSAFE
}

/* An Actual document would have a base dir */
func (an *abstractNode) BaseDir() string {
	return ""
}
<|endoftext|>


<commit_before>package nfs

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path"
	"sort"
	"strings"
	"syscall"

	"github.com/zenoss/serviced/commons/atomicfile"
)

// Server manages exporting an NFS mount.
type Server struct {
	basePath      string
	exportedName  string
	exportOptions string
	network       string
	clients       map[string]struct{}
}

var (
	// ErrInvalidExportedName is returned when an exported name is not a valid single directory name
	ErrInvalidExportedName = errors.New("nfs server: invalid exported name")
	// ErrInvalidBasePath is returned when the local path to export is invalid
	ErrInvalidBasePath = errors.New("nfs server: invalid base path")
	// ErrBasePathNotDir is returned when the base path is not a directory
	ErrBasePathNotDir = errors.New("nfs server: base path not a directory")
	// ErrInvalidNetwork is returned when the network specifier does not parse in CIDR format
	ErrInvalidNetwork = errors.New("nfs server: the network value is not CIDR")
)

var (
	exportsPath = "/exports"
)

var (
	osMkdirAll = os.MkdirAll
	osChmod    = os.Chmod
)

const defaultDirectoryPerm = 0755

const hostDenyMarker = "# serviced, do not remove past this line"
const hostDenyDefaults = "\n# serviced, do not remove past this line\nrpcbind mountd nfsd statd lockd rquotad : ALL\n"

const hostAllowMarker = "# serviced, do not remove past this line"
const hostAllowDefaults = "\n# serviced, do not remove past this line\nrpcbind mountd nfsd statd lockd rquotad : 127.0.0.1"

func verifyExportsDir(path string) error {
	stat, err := os.Stat(path)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		//handle does not exist
		return osMkdirAll(path, defaultDirectoryPerm)
	}
	if !stat.IsDir() {
		return ErrBasePathNotDir
	}
	if (stat.Mode() & defaultDirectoryPerm) != defaultDirectoryPerm {
		err = osChmod(path, defaultDirectoryPerm)
	}
	return err
}

// NewServer returns a nfs.Server object that manages the given nfs mounts to
// configured clients.
func NewServer(basePath, exportedName, network string) (*Server, error) {

	if len(exportedName) < 2 || strings.Contains(exportedName, "/") {
		return nil, ErrInvalidExportedName
	}
	if len(basePath) < 2 {
		return nil, ErrInvalidBasePath
	}
	if err := verifyExportsDir(basePath); err != nil {
		return nil, err
	}
	if err := verifyExportsDir(path.Join(exportsPath, exportedName)); err != nil {
		return nil, err
	}

	if _, _, err := net.ParseCIDR(network); err != nil {
		return nil, ErrInvalidNetwork
	}

	return &Server{
		basePath:      basePath,
		exportedName:  exportedName,
		exportOptions: "rw,nohide,insecure,no_subtree_check,async",
		clients:       make(map[string]struct{}),
		network:       network,
	}, nil
}

// ExportName returns the external export name; foo for nfs export /exports/foo
func (c *Server) ExportPath() string {
	return path.Join(exportsPath, c.exportedName)
}

// Clients returns the IP Addresses of the current clients
func (c *Server) Clients() []string {
	clients := make([]string, len(c.clients))
	i := 0
	for key := range c.clients {
		clients[i] = key
	}
	return clients
}

// SetClients replaces the existing clients with the new clients
func (c *Server) SetClients(clients ...string) {
	c.clients = make(map[string]struct{})

	for _, client := range clients {
		c.clients[client] = struct{}{}
	}
}

// Sync ensures that the nfs exports are visible to all clients
func (c *Server) Sync() error {
	if err := c.hostsDeny(); err != nil {
		return err
	}
	if err := c.hostsAllow(); err != nil {
		return err
	}
	if err := reload(); err != nil {
		return err
	}
	if err := c.writeExports(); err != nil {
		return err
	}
	return nil
}

func (c *Server) hostsDeny() error {

	s, err := readFileIfExists(hostDenyDefaults)
	if err != nil {
		return err
	}
	if strings.Contains(s, hostDenyDefaults) {
		return nil
	}

	if index := strings.Index(s, hostDenyMarker); index > 0 {
		s = s[:index-1]
	}
	s = s + hostDenyDefaults
	return atomicfile.WriteFile(etcHostsDeny, []byte(s), 0664)
}

func readFileIfExists(path string) (s string, err error) {
	var exists bool
	if exists, err = doesExists(path); err != nil {
		return s, err
	}
	if exists {
		bytes, err := ioutil.ReadFile(path)
		if err != nil {
			return s, err
		}
		s = string(bytes)
	}
	return s, nil
}

func (c *Server) hostsAllow() error {
	s, err := readFileIfExists(etcHostsAllow)
	if err != nil {
		return err
	}

	if index := strings.Index(s, hostAllowMarker); index > 0 {
		s = s[:index-1]
	}

	s = s + hostAllowDefaults
	hosts := make([]string, len(c.clients))
	i := 0
	for key := range c.clients {
		hosts[i] = key
		i++
	}
	sort.Strings(hosts)
	for _, h := range hosts {
		s = s + " " + h
	}

	return atomicfile.WriteFile(etcHostsAllow, []byte(s), 0664)
}

func (c *Server) writeExports() error {
	s := fmt.Sprintf("%s\t%s(rw,fsid=0,insecure,no_subtree_check,async)\n"+
		"%s/%s\t%s(rw,nohide,insecure,no_subtree_check,async)\n",
		exportsPath, c.network, exportsPath, c.exportedName, c.network)
	if err := os.MkdirAll(exportsDir, 0775); err != nil {
		return err
	}
	edir := exportsDir + "/" + c.exportedName
	if err := os.MkdirAll(edir, 0775); err != nil {
		return err
	}
	if err := bindMount(c.basePath, edir); err != nil {
		return err
	}
	return atomicfile.WriteFile(etcExports, []byte(s), 0664)
}

type bindMountF func(string, string) error

var bindMount = bindMountImp

// bindMountImp performs a bind mount of src to dst.
func bindMountImp(src, dst string) error {
	runMountCommand:= func (options ...string) error {
		cmd, args := mntArgs(src, dst, "", options...)
		mount := exec.Command(cmd, args...)
		return mount.Run()
	}
	returnErr := runMountCommand("bind")
	if returnErr != nil {
		// If the mount fails, it could be due to a stale NFS handle, signalled
		// by a return code of 32. Stale handle can occur if e.g., the source
		// directory has been deleted and restored (a common occurrence in the
		// dev workflow) Try again, with remount option.
		if exitError, ok := returnErr.(*exec.ExitError); ok {
			if status, ok := exitError.Sys().(syscall.WaitStatus); ok {
				if (status.ExitStatus() & 32) != 0 {
					returnErr = runMountCommand("bind", "remount")
				}
			}
		}
	}
	return returnErr
}

func doesExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

// mntArgs computes the required arguments for the mount command given
// fs (src fs), dst (mount point), fsType (ext3, xfs, etc), options (parameters
// passed to the -o flag).
func mntArgs(fs, dst, fsType string, options ...string) (cmd string, args []string) {
	args = make([]string, 0)
	if syscall.Getuid() != 0 {
		args = append(args, "sudo")
	}
	args = append(args, "mount")
	for _, option := range options{
		args = append(args, "-o")
		args = append(args, option)
	}
	if len(fsType) > 0 {
		args = append(args, "-t")
		args = append(args, fsType)
	}
	if len(fs) > 0 {
		args = append(args, fs)
	}
	args = append(args, dst)
	return args[0], args[1:]
}
<commit_msg>some versions of nfs don't like 0.0.0.0/0 use * instead<commit_after>package nfs

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"os"
	"os/exec"
	"path"
	"sort"
	"strings"
	"syscall"

	"github.com/zenoss/serviced/commons/atomicfile"
)

// Server manages exporting an NFS mount.
type Server struct {
	basePath      string
	exportedName  string
	exportOptions string
	network       string
	clients       map[string]struct{}
}

var (
	// ErrInvalidExportedName is returned when an exported name is not a valid single directory name
	ErrInvalidExportedName = errors.New("nfs server: invalid exported name")
	// ErrInvalidBasePath is returned when the local path to export is invalid
	ErrInvalidBasePath = errors.New("nfs server: invalid base path")
	// ErrBasePathNotDir is returned when the base path is not a directory
	ErrBasePathNotDir = errors.New("nfs server: base path not a directory")
	// ErrInvalidNetwork is returned when the network specifier does not parse in CIDR format
	ErrInvalidNetwork = errors.New("nfs server: the network value is not CIDR")
)

var (
	exportsPath = "/exports"
)

var (
	osMkdirAll = os.MkdirAll
	osChmod    = os.Chmod
)

const defaultDirectoryPerm = 0755

const hostDenyMarker = "# serviced, do not remove past this line"
const hostDenyDefaults = "\n# serviced, do not remove past this line\nrpcbind mountd nfsd statd lockd rquotad : ALL\n"

const hostAllowMarker = "# serviced, do not remove past this line"
const hostAllowDefaults = "\n# serviced, do not remove past this line\nrpcbind mountd nfsd statd lockd rquotad : 127.0.0.1"

func verifyExportsDir(path string) error {
	stat, err := os.Stat(path)
	if err != nil {
		if !os.IsNotExist(err) {
			return err
		}
		//handle does not exist
		return osMkdirAll(path, defaultDirectoryPerm)
	}
	if !stat.IsDir() {
		return ErrBasePathNotDir
	}
	if (stat.Mode() & defaultDirectoryPerm) != defaultDirectoryPerm {
		err = osChmod(path, defaultDirectoryPerm)
	}
	return err
}

// NewServer returns a nfs.Server object that manages the given nfs mounts to
// configured clients.
func NewServer(basePath, exportedName, network string) (*Server, error) {

	if len(exportedName) < 2 || strings.Contains(exportedName, "/") {
		return nil, ErrInvalidExportedName
	}
	if len(basePath) < 2 {
		return nil, ErrInvalidBasePath
	}
	if err := verifyExportsDir(basePath); err != nil {
		return nil, err
	}
	if err := verifyExportsDir(path.Join(exportsPath, exportedName)); err != nil {
		return nil, err
	}

	if _, _, err := net.ParseCIDR(network); err != nil {
		return nil, ErrInvalidNetwork
	}

	return &Server{
		basePath:      basePath,
		exportedName:  exportedName,
		exportOptions: "rw,nohide,insecure,no_subtree_check,async",
		clients:       make(map[string]struct{}),
		network:       network,
	}, nil
}

// ExportName returns the external export name; foo for nfs export /exports/foo
func (c *Server) ExportPath() string {
	return path.Join(exportsPath, c.exportedName)
}

// Clients returns the IP Addresses of the current clients
func (c *Server) Clients() []string {
	clients := make([]string, len(c.clients))
	i := 0
	for key := range c.clients {
		clients[i] = key
	}
	return clients
}

// SetClients replaces the existing clients with the new clients
func (c *Server) SetClients(clients ...string) {
	c.clients = make(map[string]struct{})

	for _, client := range clients {
		c.clients[client] = struct{}{}
	}
}

// Sync ensures that the nfs exports are visible to all clients
func (c *Server) Sync() error {
	if err := c.hostsDeny(); err != nil {
		return err
	}
	if err := c.hostsAllow(); err != nil {
		return err
	}
	if err := reload(); err != nil {
		return err
	}
	if err := c.writeExports(); err != nil {
		return err
	}
	return nil
}

func (c *Server) hostsDeny() error {

	s, err := readFileIfExists(hostDenyDefaults)
	if err != nil {
		return err
	}
	if strings.Contains(s, hostDenyDefaults) {
		return nil
	}

	if index := strings.Index(s, hostDenyMarker); index > 0 {
		s = s[:index-1]
	}
	s = s + hostDenyDefaults
	return atomicfile.WriteFile(etcHostsDeny, []byte(s), 0664)
}

func readFileIfExists(path string) (s string, err error) {
	var exists bool
	if exists, err = doesExists(path); err != nil {
		return s, err
	}
	if exists {
		bytes, err := ioutil.ReadFile(path)
		if err != nil {
			return s, err
		}
		s = string(bytes)
	}
	return s, nil
}

func (c *Server) hostsAllow() error {
	s, err := readFileIfExists(etcHostsAllow)
	if err != nil {
		return err
	}

	if index := strings.Index(s, hostAllowMarker); index > 0 {
		s = s[:index-1]
	}

	s = s + hostAllowDefaults
	hosts := make([]string, len(c.clients))
	i := 0
	for key := range c.clients {
		hosts[i] = key
		i++
	}
	sort.Strings(hosts)
	for _, h := range hosts {
		s = s + " " + h
	}

	return atomicfile.WriteFile(etcHostsAllow, []byte(s), 0664)
}

func (c *Server) writeExports() error {

	network := c.network
	if network == "0.0.0.0/0" {
		network = "*" // turn this in to nfs 'allow all hosts' syntax
	}
	s := fmt.Sprintf("%s\t%s(rw,fsid=0,insecure,no_subtree_check,async)\n"+
		"%s/%s\t%s(rw,nohide,insecure,no_subtree_check,async)\n",
		exportsPath, network, exportsPath, c.exportedName, network)
	if err := os.MkdirAll(exportsDir, 0775); err != nil {
		return err
	}
	edir := exportsDir + "/" + c.exportedName
	if err := os.MkdirAll(edir, 0775); err != nil {
		return err
	}
	if err := bindMount(c.basePath, edir); err != nil {
		return err
	}
	return atomicfile.WriteFile(etcExports, []byte(s), 0664)
}

type bindMountF func(string, string) error

var bindMount = bindMountImp

// bindMountImp performs a bind mount of src to dst.
func bindMountImp(src, dst string) error {
	runMountCommand:= func (options ...string) error {
		cmd, args := mntArgs(src, dst, "", options...)
		mount := exec.Command(cmd, args...)
		return mount.Run()
	}
	returnErr := runMountCommand("bind")
	if returnErr != nil {
		// If the mount fails, it could be due to a stale NFS handle, signalled
		// by a return code of 32. Stale handle can occur if e.g., the source
		// directory has been deleted and restored (a common occurrence in the
		// dev workflow) Try again, with remount option.
		if exitError, ok := returnErr.(*exec.ExitError); ok {
			if status, ok := exitError.Sys().(syscall.WaitStatus); ok {
				if (status.ExitStatus() & 32) != 0 {
					returnErr = runMountCommand("bind", "remount")
				}
			}
		}
	}
	return returnErr
}

func doesExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

// mntArgs computes the required arguments for the mount command given
// fs (src fs), dst (mount point), fsType (ext3, xfs, etc), options (parameters
// passed to the -o flag).
func mntArgs(fs, dst, fsType string, options ...string) (cmd string, args []string) {
	args = make([]string, 0)
	if syscall.Getuid() != 0 {
		args = append(args, "sudo")
	}
	args = append(args, "mount")
	for _, option := range options{
		args = append(args, "-o")
		args = append(args, option)
	}
	if len(fsType) > 0 {
		args = append(args, "-t")
		args = append(args, fsType)
	}
	if len(fs) > 0 {
		args = append(args, fs)
	}
	args = append(args, dst)
	return args[0], args[1:]
}
<|endoftext|>


<commit_before>package actor

type RouterConfig interface {
	OnStarted(context Context, props Props, router RouterState)
	Create() RouterState
}

type GroupRouterConfig interface {
	RouterConfig
	GroupRouter()
}

type PoolRouterConfig interface {
	RouterConfig
	PoolRouter()
}

func spawnRouter(config RouterConfig, props Props, parent *PID) *PID {
	id := ProcessRegistry.getAutoId()
	routeeProps := props
	routeeProps.routerConfig = nil
	routerState := config.Create()

	routerProps := FromFunc(func(context Context) {
		switch context.Message().(type) {
		case Started:
			config.OnStarted(context, routeeProps, routerState)
		}
	})
	router := spawn(id, routerProps, parent)

	ref := &RouterActorRef{
		router: router,
		state:  routerState,
	}
	proxyID := ProcessRegistry.getAutoId()
	proxy := ProcessRegistry.registerPID(ref, proxyID)
	return proxy
}

type RouterActorRef struct {
	router *PID
	state  RouterState
	ActorRef
}

func (ref *RouterActorRef) Tell(message interface{}) {
	ref.state.Route(message)
}
<commit_msg>stopping routers<commit_after>package actor

type RouterConfig interface {
	OnStarted(context Context, props Props, router RouterState)
	Create() RouterState
}

type GroupRouterConfig interface {
	RouterConfig
	GroupRouter()
}

type PoolRouterConfig interface {
	RouterConfig
	PoolRouter()
}

func spawnRouter(config RouterConfig, props Props, parent *PID) *PID {
	id := ProcessRegistry.getAutoId()
	routeeProps := props
	routeeProps.routerConfig = nil
	routerState := config.Create()

	routerProps := FromFunc(func(context Context) {
		switch context.Message().(type) {
		case Started:
			config.OnStarted(context, routeeProps, routerState)
		}
	})
	router := spawn(id, routerProps, parent)

	ref := &RouterActorRef{
		router: router,
		state:  routerState,
	}
	proxyID := ProcessRegistry.getAutoId()
	proxy := ProcessRegistry.registerPID(ref, proxyID)
	return proxy
}

type RouterActorRef struct {
	router *PID
	state  RouterState
	ActorRef
}

func (ref *RouterActorRef) Tell(message interface{}) {
	ref.state.Route(message)
}

func (ref *RouterActorRef) SendSystemMessage(message SystemMessage) {
	r, _ := ProcessRegistry.fromPID(ref.router)
	r.SendSystemMessage(message)
}

func (ref *RouterActorRef) Stop() {
	ref.SendSystemMessage(&stop{})
}
<|endoftext|>


<commit_before>package addrs

import (
	"fmt"
	"strings"
)

// Resource is an address for a resource block within configuration, which
// contains potentially-multiple resource instances if that configuration
// block uses "count" or "for_each".
type Resource struct {
	referenceable
	Mode ResourceMode
	Type string
	Name string
}

func (r Resource) String() string {
	switch r.Mode {
	case ManagedResourceMode:
		return fmt.Sprintf("%s.%s", r.Type, r.Name)
	case DataResourceMode:
		return fmt.Sprintf("data.%s.%s", r.Type, r.Name)
	default:
		// Should never happen, but we'll return a string here rather than
		// crashing just in case it does.
		return fmt.Sprintf("<invalid>.%s.%s", r.Type, r.Name)
	}
}

// Instance produces the address for a specific instance of the receiver
// that is idenfied by the given key.
func (r Resource) Instance(key InstanceKey) ResourceInstance {
	return ResourceInstance{
		Resource: r,
		Key:      key,
	}
}

// Absolute returns an AbsResource from the receiver and the given module
// instance address.
func (r Resource) Absolute(module ModuleInstance) AbsResource {
	return AbsResource{
		Module:   module,
		Resource: r,
	}
}

// DefaultProviderConfig returns the address of the provider configuration
// that should be used for the resource identified by the reciever if it
// does not have a provider configuration address explicitly set in
// configuration.
//
// This method is not able to verify that such a configuration exists, nor
// represent the behavior of automatically inheriting certain provider
// configurations from parent modules. It just does a static analysis of the
// receiving address and returns an address to start from, relative to the
// same module that contains the resource.
func (r Resource) DefaultProviderConfig() ProviderConfig {
	typeName := r.Type
	if under := strings.Index(typeName, "_"); under != -1 {
		typeName = typeName[:under]
	}
	return ProviderConfig{
		Type: typeName,
	}
}

// ResourceInstance is an address for a specific instance of a resource.
// When a resource is defined in configuration with "count" or "for_each" it
// produces zero or more instances, which can be addressed using this type.
type ResourceInstance struct {
	referenceable
	Resource Resource
	Key      InstanceKey
}

func (r ResourceInstance) ContainingResource() Resource {
	return r.Resource
}

func (r ResourceInstance) String() string {
	if r.Key == NoKey {
		return r.Resource.String()
	}
	return r.Resource.String() + r.Key.String()
}

// Absolute returns an AbsResourceInstance from the receiver and the given module
// instance address.
func (r ResourceInstance) Absolute(module ModuleInstance) AbsResourceInstance {
	return AbsResourceInstance{
		Module:   module,
		Resource: r,
	}
}

// AbsResource is an absolute address for a resource under a given module path.
type AbsResource struct {
	targetable
	Module   ModuleInstance
	Resource Resource
}

// Resource returns the address of a particular resource within the receiver.
func (m ModuleInstance) Resource(mode ResourceMode, typeName string, name string) AbsResource {
	return AbsResource{
		Module: m,
		Resource: Resource{
			Mode: mode,
			Type: typeName,
			Name: name,
		},
	}
}

// Instance produces the address for a specific instance of the receiver
// that is idenfied by the given key.
func (r AbsResource) Instance(key InstanceKey) AbsResourceInstance {
	return AbsResourceInstance{
		Module:   r.Module,
		Resource: r.Resource.Instance(key),
	}
}

// TargetContains implements Targetable by returning true if the given other
// address is either equal to the receiver or is an instance of the
// receiver.
func (r AbsResource) TargetContains(other Targetable) bool {
	switch to := other.(type) {

	case AbsResource:
		// We'll use our stringification as a cheat-ish way to test for equality.
		return to.String() == r.String()

	case AbsResourceInstance:
		return r.TargetContains(to.ContainingResource())

	default:
		return false

	}
}

func (r AbsResource) String() string {
	if len(r.Module) == 0 {
		return r.Resource.String()
	}
	return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String())
}

// AbsResourceInstance is an absolute address for a resource instance under a
// given module path.
type AbsResourceInstance struct {
	targetable
	Module   ModuleInstance
	Resource ResourceInstance
}

// ResourceInstance returns the address of a particular resource instance within the receiver.
func (m ModuleInstance) ResourceInstance(mode ResourceMode, typeName string, name string, key InstanceKey) AbsResourceInstance {
	return AbsResourceInstance{
		Module: m,
		Resource: ResourceInstance{
			Resource: Resource{
				Mode: mode,
				Type: typeName,
				Name: name,
			},
			Key: key,
		},
	}
}

// ContainingResource returns the address of the resource that contains the
// receving resource instance. In other words, it discards the key portion
// of the address to produce an AbsResource value.
func (r AbsResourceInstance) ContainingResource() AbsResource {
	return AbsResource{
		Module:   r.Module,
		Resource: r.Resource.ContainingResource(),
	}
}

// TargetContains implements Targetable by returning true if the given other
// address is equal to the receiver.
func (r AbsResourceInstance) TargetContains(other Targetable) bool {
	switch to := other.(type) {

	case AbsResourceInstance:
		// We'll use our stringification as a cheat-ish way to test for equality.
		return to.String() == r.String()

	default:
		return false

	}
}

func (r AbsResourceInstance) String() string {
	if len(r.Module) == 0 {
		return r.Resource.String()
	}
	return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String())
}

// Less returns true if the receiver should sort before the given other value
// in a sorted list of addresses.
func (r AbsResourceInstance) Less(o AbsResourceInstance) bool {
	switch {

	case len(r.Module) != len(o.Module):
		return len(r.Module) < len(o.Module)

	case r.Module.String() != o.Module.String():
		return r.Module.Less(o.Module)

	case r.Resource.Resource.Mode != o.Resource.Resource.Mode:
		return r.Resource.Resource.Mode == DataResourceMode

	case r.Resource.Resource.Type != o.Resource.Resource.Type:
		return r.Resource.Resource.Type < o.Resource.Resource.Type

	case r.Resource.Resource.Name != o.Resource.Resource.Name:
		return r.Resource.Resource.Name < o.Resource.Resource.Name

	case r.Resource.Key != o.Resource.Key:
		return InstanceKeyLess(r.Resource.Key, o.Resource.Key)

	default:
		return false

	}
}

// ResourceMode defines which lifecycle applies to a given resource. Each
// resource lifecycle has a slightly different address format.
type ResourceMode rune

//go:generate stringer -type ResourceMode

const (
	// InvalidResourceMode is the zero value of ResourceMode and is not
	// a valid resource mode.
	InvalidResourceMode ResourceMode = 0

	// ManagedResourceMode indicates a managed resource, as defined by
	// "resource" blocks in configuration.
	ManagedResourceMode ResourceMode = 'M'

	// DataResourceMode indicates a data resource, as defined by
	// "data" blocks in configuration.
	DataResourceMode ResourceMode = 'D'
)
<commit_msg>addrs: Implement Equal for resource address types<commit_after>package addrs

import (
	"fmt"
	"strings"
)

// Resource is an address for a resource block within configuration, which
// contains potentially-multiple resource instances if that configuration
// block uses "count" or "for_each".
type Resource struct {
	referenceable
	Mode ResourceMode
	Type string
	Name string
}

func (r Resource) String() string {
	switch r.Mode {
	case ManagedResourceMode:
		return fmt.Sprintf("%s.%s", r.Type, r.Name)
	case DataResourceMode:
		return fmt.Sprintf("data.%s.%s", r.Type, r.Name)
	default:
		// Should never happen, but we'll return a string here rather than
		// crashing just in case it does.
		return fmt.Sprintf("<invalid>.%s.%s", r.Type, r.Name)
	}
}

func (r Resource) Equal(other Resource) bool {
	return r.String() == other.String()
}

// Instance produces the address for a specific instance of the receiver
// that is idenfied by the given key.
func (r Resource) Instance(key InstanceKey) ResourceInstance {
	return ResourceInstance{
		Resource: r,
		Key:      key,
	}
}

// Absolute returns an AbsResource from the receiver and the given module
// instance address.
func (r Resource) Absolute(module ModuleInstance) AbsResource {
	return AbsResource{
		Module:   module,
		Resource: r,
	}
}

// DefaultProviderConfig returns the address of the provider configuration
// that should be used for the resource identified by the reciever if it
// does not have a provider configuration address explicitly set in
// configuration.
//
// This method is not able to verify that such a configuration exists, nor
// represent the behavior of automatically inheriting certain provider
// configurations from parent modules. It just does a static analysis of the
// receiving address and returns an address to start from, relative to the
// same module that contains the resource.
func (r Resource) DefaultProviderConfig() ProviderConfig {
	typeName := r.Type
	if under := strings.Index(typeName, "_"); under != -1 {
		typeName = typeName[:under]
	}
	return ProviderConfig{
		Type: typeName,
	}
}

// ResourceInstance is an address for a specific instance of a resource.
// When a resource is defined in configuration with "count" or "for_each" it
// produces zero or more instances, which can be addressed using this type.
type ResourceInstance struct {
	referenceable
	Resource Resource
	Key      InstanceKey
}

func (r ResourceInstance) ContainingResource() Resource {
	return r.Resource
}

func (r ResourceInstance) String() string {
	if r.Key == NoKey {
		return r.Resource.String()
	}
	return r.Resource.String() + r.Key.String()
}

func (r ResourceInstance) Equal(other ResourceInstance) bool {
	return r.String() == other.String()
}

// Absolute returns an AbsResourceInstance from the receiver and the given module
// instance address.
func (r ResourceInstance) Absolute(module ModuleInstance) AbsResourceInstance {
	return AbsResourceInstance{
		Module:   module,
		Resource: r,
	}
}

// AbsResource is an absolute address for a resource under a given module path.
type AbsResource struct {
	targetable
	Module   ModuleInstance
	Resource Resource
}

// Resource returns the address of a particular resource within the receiver.
func (m ModuleInstance) Resource(mode ResourceMode, typeName string, name string) AbsResource {
	return AbsResource{
		Module: m,
		Resource: Resource{
			Mode: mode,
			Type: typeName,
			Name: name,
		},
	}
}

// Instance produces the address for a specific instance of the receiver
// that is idenfied by the given key.
func (r AbsResource) Instance(key InstanceKey) AbsResourceInstance {
	return AbsResourceInstance{
		Module:   r.Module,
		Resource: r.Resource.Instance(key),
	}
}

// TargetContains implements Targetable by returning true if the given other
// address is either equal to the receiver or is an instance of the
// receiver.
func (r AbsResource) TargetContains(other Targetable) bool {
	switch to := other.(type) {

	case AbsResource:
		// We'll use our stringification as a cheat-ish way to test for equality.
		return to.String() == r.String()

	case AbsResourceInstance:
		return r.TargetContains(to.ContainingResource())

	default:
		return false

	}
}

func (r AbsResource) String() string {
	if len(r.Module) == 0 {
		return r.Resource.String()
	}
	return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String())
}

func (r AbsResource) Equal(other AbsResource) bool {
	return r.String() == other.String()
}

// AbsResourceInstance is an absolute address for a resource instance under a
// given module path.
type AbsResourceInstance struct {
	targetable
	Module   ModuleInstance
	Resource ResourceInstance
}

// ResourceInstance returns the address of a particular resource instance within the receiver.
func (m ModuleInstance) ResourceInstance(mode ResourceMode, typeName string, name string, key InstanceKey) AbsResourceInstance {
	return AbsResourceInstance{
		Module: m,
		Resource: ResourceInstance{
			Resource: Resource{
				Mode: mode,
				Type: typeName,
				Name: name,
			},
			Key: key,
		},
	}
}

// ContainingResource returns the address of the resource that contains the
// receving resource instance. In other words, it discards the key portion
// of the address to produce an AbsResource value.
func (r AbsResourceInstance) ContainingResource() AbsResource {
	return AbsResource{
		Module:   r.Module,
		Resource: r.Resource.ContainingResource(),
	}
}

// TargetContains implements Targetable by returning true if the given other
// address is equal to the receiver.
func (r AbsResourceInstance) TargetContains(other Targetable) bool {
	switch to := other.(type) {

	case AbsResourceInstance:
		// We'll use our stringification as a cheat-ish way to test for equality.
		return to.String() == r.String()

	default:
		return false

	}
}

func (r AbsResourceInstance) String() string {
	if len(r.Module) == 0 {
		return r.Resource.String()
	}
	return fmt.Sprintf("%s.%s", r.Module.String(), r.Resource.String())
}

func (r AbsResourceInstance) Equal(other AbsResourceInstance) bool {
	return r.String() == other.String()
}

// Less returns true if the receiver should sort before the given other value
// in a sorted list of addresses.
func (r AbsResourceInstance) Less(o AbsResourceInstance) bool {
	switch {

	case len(r.Module) != len(o.Module):
		return len(r.Module) < len(o.Module)

	case r.Module.String() != o.Module.String():
		return r.Module.Less(o.Module)

	case r.Resource.Resource.Mode != o.Resource.Resource.Mode:
		return r.Resource.Resource.Mode == DataResourceMode

	case r.Resource.Resource.Type != o.Resource.Resource.Type:
		return r.Resource.Resource.Type < o.Resource.Resource.Type

	case r.Resource.Resource.Name != o.Resource.Resource.Name:
		return r.Resource.Resource.Name < o.Resource.Resource.Name

	case r.Resource.Key != o.Resource.Key:
		return InstanceKeyLess(r.Resource.Key, o.Resource.Key)

	default:
		return false

	}
}

// ResourceMode defines which lifecycle applies to a given resource. Each
// resource lifecycle has a slightly different address format.
type ResourceMode rune

//go:generate stringer -type ResourceMode

const (
	// InvalidResourceMode is the zero value of ResourceMode and is not
	// a valid resource mode.
	InvalidResourceMode ResourceMode = 0

	// ManagedResourceMode indicates a managed resource, as defined by
	// "resource" blocks in configuration.
	ManagedResourceMode ResourceMode = 'M'

	// DataResourceMode indicates a data resource, as defined by
	// "data" blocks in configuration.
	DataResourceMode ResourceMode = 'D'
)
<|endoftext|>


<commit_before>package amsutil

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"mime"
	"os"
	"path"
	"strings"
	"time"

	"github.com/pkg/errors"
	"github.com/recruit-tech/go-ams"
)

const (
	uploadPolicyName       = "UploadPolicy"
	uploadDurationInMinute = 440.0
)

type multiError struct {
	errs []error
}

func (m *multiError) Error() string {
	messages := make([]string, 0, len(m.errs))
	for _, err := range m.errs {
		messages = append(messages, err.Error())
	}
	return fmt.Sprintf("multiple error: %s", strings.Join(messages, ","))
}

type Uploadable interface {
	Name() string
	Size() int64
	Blobs() []ams.Blob
}

type uploadable struct {
	name  string
	size  int64
	blobs []ams.Blob
}

func (u *uploadable) Name() string      { return u.name }
func (u *uploadable) Size() int64       { return u.size }
func (u *uploadable) Blobs() []ams.Blob { return u.blobs }

func SplitBlob(blob ams.Blob, chunkSize int64) ([]ams.Blob, error) {
	size := blob.Size()
	body, err := ioutil.ReadAll(blob)
	if err != nil {
		return nil, errors.Wrap(err, "blob failed to read")
	}
	var chunk []byte
	chunks := make([]ams.Blob, 0, (size+chunkSize-1)/chunkSize)
	for int64(len(body)) >= chunkSize {
		chunk, body = body[:chunkSize], body[chunkSize:]
		chunks = append(chunks, bytes.NewReader(chunk))
	}
	if len(body) > 0 {
		chunks = append(chunks, bytes.NewReader(body))
	}
	return chunks, nil
}

func NewUploadble(name string, blob ams.Blob, chunkSize int64) (Uploadable, error) {
	blobs, err := SplitBlob(blob, chunkSize)
	if err != nil {
		return nil, errors.Wrap(err, "blob failed to split")
	}
	return &uploadable{
		name:  name,
		size:  blob.Size(),
		blobs: blobs,
	}, nil
}

func UploadFile(ctx context.Context, client *ams.Client, file *os.File, chunkSize, worker int64) (*ams.Asset, error) {
	if client == nil {
		return nil, errors.New("client missing")
	}
	if file == nil {
		return nil, errors.New("file missing")
	}

	fblob, err := ams.NewFileBlob(file)
	if err != nil {
		return nil, errors.Wrap(err, "file blob construct failed")
	}

	mimeType := mime.TypeByExtension(path.Ext(fblob.Name()))
	if !strings.HasPrefix(mimeType, "video/") {
		return nil, errors.Errorf("invalid file type. expected video/*, actual '%v'", mimeType)
	}

	uploadable, err := NewUploadble(fblob.Name(), fblob, chunkSize)
	if err != nil {
		return nil, errors.Wrap(err, "uploadable failed to construct")
	}

	return Upload(ctx, client, uploadable, mimeType, worker)
}

func Upload(ctx context.Context, client *ams.Client, uploadable Uploadable, mimeType string, worker int64) (*ams.Asset, error) {
	if client == nil {
		return nil, errors.New("client missing")
	}
	if uploadable == nil {
		return nil, errors.New("uploadable missing")
	}

	name := uploadable.Name()
	asset, err := client.CreateAsset(ctx, name)
	if err != nil {
		return nil, errors.Wrapf(err, "create asset failed. name='%s'", name)
	}

	assetFile, err := client.CreateAssetFile(ctx, asset.ID, name, mimeType)
	if err != nil {
		return nil, errors.Wrapf(err, "create asset file failed. assetID='%s'", asset.ID)
	}

	accessPolicy, err := client.CreateAccessPolicy(ctx, uploadPolicyName, uploadDurationInMinute, ams.PermissionWrite)
	if err != nil {
		return nil, errors.Wrap(err, "create access policy failed")
	}
	defer client.DeleteAccessPolicy(ctx, accessPolicy.ID)

	startTime := time.Now().Add(-5 * time.Minute)
	locator, err := client.CreateLocator(ctx, accessPolicy.ID, asset.ID, startTime, ams.LocatorSAS)
	if err != nil {
		return nil, errors.Wrap(err, "create locator failed")
	}
	defer client.DeleteLocator(ctx, locator.ID)

	uploadURL, err := locator.ToUploadURL(name)
	if err != nil {
		return nil, errors.Wrapf(err, "upload url build failed")
	}

	blobs := uploadable.Blobs()
	blobsSize := len(blobs)
	jobs := make(chan int, worker)
	errs := make(chan error, worker)

	for w := int64(0); w < worker; w++ {
		go func(c context.Context, id int64, in <-chan int, e chan<- error) {
			for i := range in {
				blockID := fmt.Sprintf("block-id-%v", i+1)
				e <- client.PutBlob(c, uploadURL, blobs[i], blockID)
			}
		}(ctx, w, jobs, errs)
	}

	var blockList []string
	for i := 0; i < blobsSize; i++ {
		blockID := fmt.Sprintf("block-id-%v", i+1)
		blockList = append(blockList, blockID)
		jobs <- i
	}
	close(jobs)

	var es []error
	for i := 0; i < blobsSize; i++ {
		err := <-errs
		if err != nil {
			es = append(es, err)
		}
	}

	if len(es) != 0 {
		return nil, &multiError{es}
	}

	if err := client.PutBlockList(ctx, uploadURL, blockList); err != nil {
		return nil, errors.Wrap(err, "put block list failed")
	}

	assetFile.ContentFileSize = fmt.Sprint(uploadable.Size())
	if err := client.UpdateAssetFile(ctx, assetFile); err != nil {
		return nil, errors.Wrap(err, "update asset file failed")
	}

	return asset, nil
}
<commit_msg>chore: fix typo<commit_after>package amsutil

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"mime"
	"os"
	"path"
	"strings"
	"time"

	"github.com/pkg/errors"
	"github.com/recruit-tech/go-ams"
)

const (
	uploadPolicyName       = "UploadPolicy"
	uploadDurationInMinute = 440.0
)

type multiError struct {
	errs []error
}

func (m *multiError) Error() string {
	messages := make([]string, 0, len(m.errs))
	for _, err := range m.errs {
		messages = append(messages, err.Error())
	}
	return fmt.Sprintf("multiple error: %s", strings.Join(messages, ","))
}

type Uploadable interface {
	Name() string
	Size() int64
	Blobs() []ams.Blob
}

type uploadable struct {
	name  string
	size  int64
	blobs []ams.Blob
}

func (u *uploadable) Name() string      { return u.name }
func (u *uploadable) Size() int64       { return u.size }
func (u *uploadable) Blobs() []ams.Blob { return u.blobs }

func SplitBlob(blob ams.Blob, chunkSize int64) ([]ams.Blob, error) {
	size := blob.Size()
	body, err := ioutil.ReadAll(blob)
	if err != nil {
		return nil, errors.Wrap(err, "blob failed to read")
	}
	var chunk []byte
	chunks := make([]ams.Blob, 0, (size+chunkSize-1)/chunkSize)
	for int64(len(body)) >= chunkSize {
		chunk, body = body[:chunkSize], body[chunkSize:]
		chunks = append(chunks, bytes.NewReader(chunk))
	}
	if len(body) > 0 {
		chunks = append(chunks, bytes.NewReader(body))
	}
	return chunks, nil
}

func NewUploadable(name string, blob ams.Blob, chunkSize int64) (Uploadable, error) {
	blobs, err := SplitBlob(blob, chunkSize)
	if err != nil {
		return nil, errors.Wrap(err, "blob failed to split")
	}
	return &uploadable{
		name:  name,
		size:  blob.Size(),
		blobs: blobs,
	}, nil
}

func UploadFile(ctx context.Context, client *ams.Client, file *os.File, chunkSize, worker int64) (*ams.Asset, error) {
	if client == nil {
		return nil, errors.New("client missing")
	}
	if file == nil {
		return nil, errors.New("file missing")
	}

	fblob, err := ams.NewFileBlob(file)
	if err != nil {
		return nil, errors.Wrap(err, "file blob construct failed")
	}

	mimeType := mime.TypeByExtension(path.Ext(fblob.Name()))
	if !strings.HasPrefix(mimeType, "video/") {
		return nil, errors.Errorf("invalid file type. expected video/*, actual '%v'", mimeType)
	}

	uploadable, err := NewUploadable(fblob.Name(), fblob, chunkSize)
	if err != nil {
		return nil, errors.Wrap(err, "uploadable failed to construct")
	}

	return Upload(ctx, client, uploadable, mimeType, worker)
}

func Upload(ctx context.Context, client *ams.Client, uploadable Uploadable, mimeType string, worker int64) (*ams.Asset, error) {
	if client == nil {
		return nil, errors.New("client missing")
	}
	if uploadable == nil {
		return nil, errors.New("uploadable missing")
	}

	name := uploadable.Name()
	asset, err := client.CreateAsset(ctx, name)
	if err != nil {
		return nil, errors.Wrapf(err, "create asset failed. name='%s'", name)
	}

	assetFile, err := client.CreateAssetFile(ctx, asset.ID, name, mimeType)
	if err != nil {
		return nil, errors.Wrapf(err, "create asset file failed. assetID='%s'", asset.ID)
	}

	accessPolicy, err := client.CreateAccessPolicy(ctx, uploadPolicyName, uploadDurationInMinute, ams.PermissionWrite)
	if err != nil {
		return nil, errors.Wrap(err, "create access policy failed")
	}
	defer client.DeleteAccessPolicy(ctx, accessPolicy.ID)

	startTime := time.Now().Add(-5 * time.Minute)
	locator, err := client.CreateLocator(ctx, accessPolicy.ID, asset.ID, startTime, ams.LocatorSAS)
	if err != nil {
		return nil, errors.Wrap(err, "create locator failed")
	}
	defer client.DeleteLocator(ctx, locator.ID)

	uploadURL, err := locator.ToUploadURL(name)
	if err != nil {
		return nil, errors.Wrapf(err, "upload url build failed")
	}

	blobs := uploadable.Blobs()
	blobsSize := len(blobs)
	jobs := make(chan int, worker)
	errs := make(chan error, worker)

	for w := int64(0); w < worker; w++ {
		go func(c context.Context, id int64, in <-chan int, e chan<- error) {
			for i := range in {
				blockID := fmt.Sprintf("block-id-%v", i+1)
				e <- client.PutBlob(c, uploadURL, blobs[i], blockID)
			}
		}(ctx, w, jobs, errs)
	}

	var blockList []string
	for i := 0; i < blobsSize; i++ {
		blockID := fmt.Sprintf("block-id-%v", i+1)
		blockList = append(blockList, blockID)
		jobs <- i
	}
	close(jobs)

	var es []error
	for i := 0; i < blobsSize; i++ {
		err := <-errs
		if err != nil {
			es = append(es, err)
		}
	}

	if len(es) != 0 {
		return nil, &multiError{es}
	}

	if err := client.PutBlockList(ctx, uploadURL, blockList); err != nil {
		return nil, errors.Wrap(err, "put block list failed")
	}

	assetFile.ContentFileSize = fmt.Sprint(uploadable.Size())
	if err := client.UpdateAssetFile(ctx, assetFile); err != nil {
		return nil, errors.Wrap(err, "update asset file failed")
	}

	return asset, nil
}
<|endoftext|>


<commit_before>package test

// didPanic returns true if the function passed to it panics. Otherwise, it returns false.
func didPanic(panicTestFunc func()) (bool, string) {

	didPanic := false
	var message interface{}
	func() {

		defer func() {
			if message = recover(); message != nil {
				didPanic = true
			}
		}()

		// call the target function
		panicTestFunc()

	}()
	return didPanic, message.(string)
}<commit_msg>update didPanic() to handle instances of no panic<commit_after>package test

// didPanic returns true if the function passed to it panics. Otherwise, it returns false.
func didPanic(panicTestFunc func()) (bool, string) {

	didPanic := false
	var message interface{}
	func() {

		defer func() {
			if message = recover(); message != nil {
				didPanic = true
			}
		}()

		// call the target function
		panicTestFunc()

	}()
	if message == nil {
		message = ""
	}
	return didPanic, message.(string)
}<|endoftext|>