<commit_before>// Copyright 2013 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package repository

import (
	"fmt"
	"github.com/andreaskoch/allmark/markdown"
	"github.com/andreaskoch/allmark/path"
	"github.com/andreaskoch/allmark/util"
	"github.com/andreaskoch/allmark/view"
	"github.com/andreaskoch/allmark/watcher"
	"io/ioutil"
	"path/filepath"
	"time"
)

const (
	FilesDirectoryName = "files"
)

type Item struct {
	*view.Model

	Modified chan bool
	Moved    chan bool

	Level  int
	Files  *FileIndex
	Parent *Item
	Childs []*Item

	Title            string
	Description      string
	RawContent       []string
	ConvertedContent string
	MetaData         MetaData

	AbsolutePath string
	RelativePath string

	ChildsReady  chan bool
	newChild     chan *Item
	deletedChild chan *Item

	directory string
	path      string
	isVirtual bool

	rootPathProvider     *path.Provider
	relativePathProvider *path.Provider
	filePathProvider     *path.Provider
}

func newItem(rootPathProvider *path.Provider, parent *Item, itemPath string, level int, newItem chan *Item, deletedItem chan *Item) (*Item, error) {

	// abort if path does not exist
	if !util.PathExists(itemPath) {
		return nil, fmt.Errorf("The path %q does not exist.", itemPath)
	}

	// abort if path is reserved
	if isReservedDirectory(itemPath) {
		return nil, fmt.Errorf("The path %q is using a reserved name and cannot be an item.", itemPath)
	}

	// check if its a virtual item or a markdown item
	isVirtualItem := false
	itemDirectory := filepath.Dir(itemPath)

	if isDirectory, _ := util.IsDirectory(itemPath); isDirectory {

		if found, filepath := findMarkdownFileInDirectory(itemPath); found {

			itemDirectory = itemPath
			itemPath = filepath

		} else {

			isVirtualItem = true
			itemDirectory = itemPath

		}

	} else if !markdown.IsMarkdownFile(itemPath) {

		// the supplied item path does not point to a markdown file
		return nil, fmt.Errorf("%q is not a markdown file.", itemPath)
	}

	// create a relative path provider
	pathProviderDirectory := itemDirectory
	if level > 0 {
		pathProviderDirectory = filepath.Dir(itemDirectory)
	}

	relativePathProvider := rootPathProvider.New(pathProviderDirectory)

	// create a file path provider
	filePathProvider := rootPathProvider.New(itemDirectory)

	// create the file index
	filesDirectory := filepath.Join(itemDirectory, FilesDirectoryName)
	fileIndex, err := newFileIndex(rootPathProvider, filesDirectory)
	if err != nil {
		return nil, fmt.Errorf("Could not create a file index for folder %q.\nError: %s\n", filesDirectory, err)
	}

	// create the item
	item := &Item{

		ChildsReady: make(chan bool),
		Modified:    make(chan bool),
		Moved:       make(chan bool),

		Parent: parent,
		Level:  level,
		Files:  fileIndex,

		newChild:     newItem,
		deletedChild: deletedItem,

		rootPathProvider:     rootPathProvider,
		relativePathProvider: relativePathProvider,
		filePathProvider:     filePathProvider,

		directory: itemDirectory,
		path:      itemPath,
		isVirtual: isVirtualItem,
	}

	// assign paths
	item.RelativePath = item.RelativePathProvider().GetWebRoute(item)
	item.AbsolutePath = item.RootPathProvider().GetWebRoute(item)

	// find childs
	item.updateChilds()

	// look for changes to the markdown file (if the item is not virtual)
	if !item.isVirtual {
		item.startFileWatcher()
	}

	// look for changes in the item directory
	go func() {
		var skipFunc = func(path string) bool {
			isItem := path == item.path
			isReserved := isReservedDirectory(path)
			return isItem || isReserved
		}

		folderWatcher := watcher.NewFolderWatcher(itemDirectory, false, skipFunc).Start()

		for folderWatcher.IsRunning() {

			select {
			case <-folderWatcher.Change:

				if item.isVirtual {
					if found, filepath := findMarkdownFileInDirectory(itemDirectory); found {

						fmt.Printf("Converting the virtual item %q into a physical item\n", item)

						// make the item physical
						item.path = filepath
						item.isVirtual = false
						item.startFileWatcher()
					}
				}

				fmt.Printf("Updating the childs of item %q\n", item)
				item.updateChilds()

				go func() {
					time.Sleep(time.Second * 3)
					item.Modified <- true
				}()
			}

		}
	}()

	// look for file changes
	go func() {
		for {
			select {
			case <-item.Files.Changed:
				fmt.Printf("Files of item %q changed\n", item)
				item.Modified <- true
			case <-item.Files.Stopped:
				fmt.Printf("File index watcher for item %q was stopped\n", item)
				break
			}
		}
	}()

	return item, nil
}

func (item *Item) startFileWatcher() (started bool, itemWatcher *watcher.FileWatcher) {
	if isFile, _ := util.IsFile(item.path); !isFile {
		return false, nil
	}

	fileWatcher := watcher.NewFileWatcher(item.path).Start()

	go func() {

		for fileWatcher.IsRunning() {

			select {
			case <-fileWatcher.Modified:

				fmt.Printf("Item %q has been modified\n", item)
				item.Modified <- true

			case <-fileWatcher.Moved:

				fmt.Printf("Item %q has been moved\n", item)
				item.Moved <- true
			}

		}
	}()

	return true, fileWatcher
}

func (item *Item) String() string {
	return fmt.Sprintf("%s", item.RootPathProvider().GetWebRoute(item))
}

func (item *Item) Less(otherItem *Item) bool {
	return item.MetaData.Date.Before(otherItem.MetaData.Date)
}

func (item *Item) Path() string {
	return item.path
}

func (item *Item) Directory() string {
	return item.directory
}

func (item *Item) PathType() string {
	return path.PatherTypeItem
}

func (item *Item) IsVirtual() bool {
	return item.isVirtual
}

func (item *Item) RootPathProvider() *path.Provider {
	return item.rootPathProvider
}

func (item *Item) RelativePathProvider() *path.Provider {
	return item.relativePathProvider
}

func (item *Item) FilePathProvider() *path.Provider {
	return item.filePathProvider
}

func (item *Item) HasChild(itemDirectory string) bool {
	if item.Childs == nil {
		return false
	}

	for _, childItem := range item.Childs {
		if childItem.Directory() == itemDirectory {
			return true
		}
	}

	return false
}

func (item *Item) updateChilds() {

	childItemDirectories := item.getChildItemDirectories()

	// check if the child list needs initialization
	if item.Childs == nil {
		item.Childs = make([]*Item, 0, len(childItemDirectories))
	}

	// add new childs
	for _, childItemDirectory := range childItemDirectories {

		if item.HasChild(childItemDirectory) {
			continue // Child already present
		}

		if child, err := newItem(item.RootPathProvider(), item, childItemDirectory, item.Level+1, item.newChild, item.deletedChild); err == nil {

			// inform others about the new child
			go func() {
				item.newChild <- child
			}()

			item.Childs = append(item.Childs, child) // append new child

		} else {
			fmt.Printf("Could not create a item for folder %q. Error: %s\n", childItemDirectory, err)
		}
	}

	// remove deleted childs
	newChildList := make([]*Item, 0, len(childItemDirectories))
	for _, child := range item.Childs {

		if util.SliceContainsElement(childItemDirectories, child.Directory()) {

			newChildList = append(newChildList, child)

		} else {

			// Todo: stop all go routines on this child

			// inform others about the removed child
			go func() {
				item.deletedChild <- child
			}()

		}
	}

	// assign new child list
	go func() {
		item.ChildsReady <- true
	}()

	item.Childs = newChildList
}

func (item *Item) getChildItemDirectories() []string {

	directory := item.Directory()
	directories := make([]string, 0)

	directoryEntries, _ := ioutil.ReadDir(directory)
	for _, entry := range directoryEntries {

		if !entry.IsDir() {
			continue // skip files
		}

		childDirectory := filepath.Join(directory, entry.Name())
		if isReservedDirectory(childDirectory) {
			continue // skip reserved directories
		}

		// append directory
		directories = append(directories, childDirectory)
	}

	return directories
}
<commit_msg>Item sorting: Reverse the Less function. Newer items should be listed before older items<commit_after>// Copyright 2013 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package repository

import (
	"fmt"
	"github.com/andreaskoch/allmark/markdown"
	"github.com/andreaskoch/allmark/path"
	"github.com/andreaskoch/allmark/util"
	"github.com/andreaskoch/allmark/view"
	"github.com/andreaskoch/allmark/watcher"
	"io/ioutil"
	"path/filepath"
	"time"
)

const (
	FilesDirectoryName = "files"
)

type Item struct {
	*view.Model

	Modified chan bool
	Moved    chan bool

	Level  int
	Files  *FileIndex
	Parent *Item
	Childs []*Item

	Title            string
	Description      string
	RawContent       []string
	ConvertedContent string
	MetaData         MetaData

	AbsolutePath string
	RelativePath string

	ChildsReady  chan bool
	newChild     chan *Item
	deletedChild chan *Item

	directory string
	path      string
	isVirtual bool

	rootPathProvider     *path.Provider
	relativePathProvider *path.Provider
	filePathProvider     *path.Provider
}

func newItem(rootPathProvider *path.Provider, parent *Item, itemPath string, level int, newItem chan *Item, deletedItem chan *Item) (*Item, error) {

	// abort if path does not exist
	if !util.PathExists(itemPath) {
		return nil, fmt.Errorf("The path %q does not exist.", itemPath)
	}

	// abort if path is reserved
	if isReservedDirectory(itemPath) {
		return nil, fmt.Errorf("The path %q is using a reserved name and cannot be an item.", itemPath)
	}

	// check if its a virtual item or a markdown item
	isVirtualItem := false
	itemDirectory := filepath.Dir(itemPath)

	if isDirectory, _ := util.IsDirectory(itemPath); isDirectory {

		if found, filepath := findMarkdownFileInDirectory(itemPath); found {

			itemDirectory = itemPath
			itemPath = filepath

		} else {

			isVirtualItem = true
			itemDirectory = itemPath

		}

	} else if !markdown.IsMarkdownFile(itemPath) {

		// the supplied item path does not point to a markdown file
		return nil, fmt.Errorf("%q is not a markdown file.", itemPath)
	}

	// create a relative path provider
	pathProviderDirectory := itemDirectory
	if level > 0 {
		pathProviderDirectory = filepath.Dir(itemDirectory)
	}

	relativePathProvider := rootPathProvider.New(pathProviderDirectory)

	// create a file path provider
	filePathProvider := rootPathProvider.New(itemDirectory)

	// create the file index
	filesDirectory := filepath.Join(itemDirectory, FilesDirectoryName)
	fileIndex, err := newFileIndex(rootPathProvider, filesDirectory)
	if err != nil {
		return nil, fmt.Errorf("Could not create a file index for folder %q.\nError: %s\n", filesDirectory, err)
	}

	// create the item
	item := &Item{

		ChildsReady: make(chan bool),
		Modified:    make(chan bool),
		Moved:       make(chan bool),

		Parent: parent,
		Level:  level,
		Files:  fileIndex,

		newChild:     newItem,
		deletedChild: deletedItem,

		rootPathProvider:     rootPathProvider,
		relativePathProvider: relativePathProvider,
		filePathProvider:     filePathProvider,

		directory: itemDirectory,
		path:      itemPath,
		isVirtual: isVirtualItem,
	}

	// assign paths
	item.RelativePath = item.RelativePathProvider().GetWebRoute(item)
	item.AbsolutePath = item.RootPathProvider().GetWebRoute(item)

	// find childs
	item.updateChilds()

	// look for changes to the markdown file (if the item is not virtual)
	if !item.isVirtual {
		item.startFileWatcher()
	}

	// look for changes in the item directory
	go func() {
		var skipFunc = func(path string) bool {
			isItem := path == item.path
			isReserved := isReservedDirectory(path)
			return isItem || isReserved
		}

		folderWatcher := watcher.NewFolderWatcher(itemDirectory, false, skipFunc).Start()

		for folderWatcher.IsRunning() {

			select {
			case <-folderWatcher.Change:

				if item.isVirtual {
					if found, filepath := findMarkdownFileInDirectory(itemDirectory); found {

						fmt.Printf("Converting the virtual item %q into a physical item\n", item)

						// make the item physical
						item.path = filepath
						item.isVirtual = false
						item.startFileWatcher()
					}
				}

				fmt.Printf("Updating the childs of item %q\n", item)
				item.updateChilds()

				go func() {
					time.Sleep(time.Second * 3)
					item.Modified <- true
				}()
			}

		}
	}()

	// look for file changes
	go func() {
		for {
			select {
			case <-item.Files.Changed:
				fmt.Printf("Files of item %q changed\n", item)
				item.Modified <- true
			case <-item.Files.Stopped:
				fmt.Printf("File index watcher for item %q was stopped\n", item)
				break
			}
		}
	}()

	return item, nil
}

func (item *Item) startFileWatcher() (started bool, itemWatcher *watcher.FileWatcher) {
	if isFile, _ := util.IsFile(item.path); !isFile {
		return false, nil
	}

	fileWatcher := watcher.NewFileWatcher(item.path).Start()

	go func() {

		for fileWatcher.IsRunning() {

			select {
			case <-fileWatcher.Modified:

				fmt.Printf("Item %q has been modified\n", item)
				item.Modified <- true

			case <-fileWatcher.Moved:

				fmt.Printf("Item %q has been moved\n", item)
				item.Moved <- true
			}

		}
	}()

	return true, fileWatcher
}

func (item *Item) String() string {
	return fmt.Sprintf("%s", item.RootPathProvider().GetWebRoute(item))
}

func (item *Item) Less(otherItem *Item) bool {
	return item.MetaData.Date.After(otherItem.MetaData.Date)
}

func (item *Item) Path() string {
	return item.path
}

func (item *Item) Directory() string {
	return item.directory
}

func (item *Item) PathType() string {
	return path.PatherTypeItem
}

func (item *Item) IsVirtual() bool {
	return item.isVirtual
}

func (item *Item) RootPathProvider() *path.Provider {
	return item.rootPathProvider
}

func (item *Item) RelativePathProvider() *path.Provider {
	return item.relativePathProvider
}

func (item *Item) FilePathProvider() *path.Provider {
	return item.filePathProvider
}

func (item *Item) HasChild(itemDirectory string) bool {
	if item.Childs == nil {
		return false
	}

	for _, childItem := range item.Childs {
		if childItem.Directory() == itemDirectory {
			return true
		}
	}

	return false
}

func (item *Item) updateChilds() {

	childItemDirectories := item.getChildItemDirectories()

	// check if the child list needs initialization
	if item.Childs == nil {
		item.Childs = make([]*Item, 0, len(childItemDirectories))
	}

	// add new childs
	for _, childItemDirectory := range childItemDirectories {

		if item.HasChild(childItemDirectory) {
			continue // Child already present
		}

		if child, err := newItem(item.RootPathProvider(), item, childItemDirectory, item.Level+1, item.newChild, item.deletedChild); err == nil {

			// inform others about the new child
			go func() {
				item.newChild <- child
			}()

			item.Childs = append(item.Childs, child) // append new child

		} else {
			fmt.Printf("Could not create a item for folder %q. Error: %s\n", childItemDirectory, err)
		}
	}

	// remove deleted childs
	newChildList := make([]*Item, 0, len(childItemDirectories))
	for _, child := range item.Childs {

		if util.SliceContainsElement(childItemDirectories, child.Directory()) {

			newChildList = append(newChildList, child)

		} else {

			// Todo: stop all go routines on this child

			// inform others about the removed child
			go func() {
				item.deletedChild <- child
			}()

		}
	}

	// assign new child list
	go func() {
		item.ChildsReady <- true
	}()

	item.Childs = newChildList
}

func (item *Item) getChildItemDirectories() []string {

	directory := item.Directory()
	directories := make([]string, 0)

	directoryEntries, _ := ioutil.ReadDir(directory)
	for _, entry := range directoryEntries {

		if !entry.IsDir() {
			continue // skip files
		}

		childDirectory := filepath.Join(directory, entry.Name())
		if isReservedDirectory(childDirectory) {
			continue // skip reserved directories
		}

		// append directory
		directories = append(directories, childDirectory)
	}

	return directories
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"crypto/tls"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	ini "gopkg.in/ini.v1"

	"github.com/mengzhuo/bla"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

const (
	DefaultConfig = "config.ini"
)

var (
	configPath = flag.String("config", DefaultConfig, "default config path")
	version    = flag.Bool("version", false, "show version")

	logPool = sync.Pool{New: func() interface{} { return &LogWriter{nil, 200} }}
	tlsCert *tls.Certificate
	server  *http.Server
	Version string = "dev"
)

func main() {
	// defer profile.Start().Stop()
	flag.Parse()

	if *version {
		fmt.Println("bla version:", Version)
		return
	}

	raw, err := ini.Load(*configPath)
	if err != nil {
		log.Fatal(err)
	}

	raw.MapTo(cfg)

	log.Printf("pid:%d", os.Getpid())

	if cfg.MetricListen != "" {
		go loadMetric(cfg.MetricListen)
	}

	log.Printf("Server:%v", cfg)

	h := bla.NewHandler(*configPath)

	lh := logTimeAndStatus(h)
	server = &http.Server{Addr: cfg.Listen, Handler: lh}

	if cfg.Certfile != "" && cfg.Keyfile != "" {
		// for higher score in ssllab
		server.TLSConfig = &tls.Config{
			MinVersion:               tls.VersionTLS12,
			PreferServerCipherSuites: true,
		}
		server.TLSConfig.GetCertificate = getCertificate
		log.Printf("TLS:%s, %s", cfg.Certfile, cfg.Keyfile)
		watchReloadCert()
		log.Fatal(server.ListenAndServeTLS(cfg.Certfile, cfg.Keyfile))
	}
	server.ListenAndServe()

}

func logTimeAndStatus(handler http.Handler) http.Handler {

	var (
		writer io.Writer
		err    error
	)

	if cfg.AccessLogPath != "" {
		var file *os.File
		file, err = os.OpenFile(cfg.AccessLogPath,
			os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
		if err != nil {
			log.Fatal(err)
		}
		writer = bufio.NewWriter(file)
		log.Printf("Access Log to file: %s", cfg.AccessLogPath)
	} else {
		writer = os.Stdout
	}

	accessLogger := log.New(writer, "", log.LstdFlags)

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		writer := logPool.Get().(*LogWriter)
		writer.ResponseWriter = w
		writer.statusCode = 200

		if cfg.Certfile != "" {
			writer.ResponseWriter.Header().Add("Strict-Transport-Security", "max-age=31536000")
		}
		handler.ServeHTTP(writer, r)

		delta := time.Now().Sub(start)
		accessLogger.Printf("%s %s %s %s %d",
			r.RemoteAddr, r.Method, r.URL.Path,
			delta, writer.statusCode)

		httpRequestDurationSeconds.Observe(delta.Seconds())
		httpRequestCount.Inc()

		logPool.Put(writer)
	})
}

type LogWriter struct {
	http.ResponseWriter
	statusCode int
}

func (l *LogWriter) WriteHeader(i int) {
	l.statusCode = i
	l.ResponseWriter.WriteHeader(i)
}

func loadCertificate() {

	log.Println("Loading new certs")
	cert, err := tls.LoadX509KeyPair(cfg.Certfile, cfg.Keyfile)
	if err != nil {
		log.Println("load cert failed keep old", err)
		return
	}
	tlsCert = &cert
}

func watchReloadCert() {
	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, syscall.SIGUSR1)
		for range c {
			log.Print("got reload cert signal")
			loadCertificate()
		}
	}()
}

func getCertificate(ch *tls.ClientHelloInfo) (cert *tls.Certificate, err error) {
	return tlsCert, nil
}

// Config ---------------------

var cfg *ServerConfig

type ServerConfig struct {
	Certfile      string
	Keyfile       string
	Listen        string
	MetricListen  string
	AccessLogPath string
}

func init() {
	cfg = &ServerConfig{
		"", "", ":8080",
		"access.log", ""}
}

func loadMetric(addr string) {

	prometheus.MustRegister(httpRequestCount)
	prometheus.MustRegister(httpRequestDurationSeconds)
	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(addr, nil)
}

var (
	httpRequestCount = prometheus.NewCounter(prometheus.CounterOpts{
		Namespace: "http",
		Subsystem: "request",
		Name:      "requests_count",
		Help:      "The total number of http request",
	})
	httpRequestDurationSeconds = prometheus.NewSummary(
		prometheus.SummaryOpts{
			Namespace: "http",
			Subsystem: "request",
			Name:      "duration_seconds",
			Help:      "The request duration distribution",
		})
)
<commit_msg>reset accesslog back to file<commit_after>package main

import (
	"crypto/tls"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	ini "gopkg.in/ini.v1"

	"github.com/mengzhuo/bla"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

const (
	DefaultConfig = "config.ini"
)

var (
	configPath = flag.String("config", DefaultConfig, "default config path")
	version    = flag.Bool("version", false, "show version")

	logPool = sync.Pool{New: func() interface{} { return &LogWriter{nil, 200} }}
	tlsCert *tls.Certificate
	server  *http.Server
	Version string = "dev"
)

func main() {
	// defer profile.Start().Stop()
	flag.Parse()

	if *version {
		fmt.Println("bla version:", Version)
		return
	}

	raw, err := ini.Load(*configPath)
	if err != nil {
		log.Fatal(err)
	}

	raw.MapTo(cfg)

	log.Printf("pid:%d", os.Getpid())

	if cfg.MetricListen != "" {
		go loadMetric(cfg.MetricListen)
	}

	log.Printf("Server:%v", cfg)

	h := bla.NewHandler(*configPath)

	lh := logTimeAndStatus(h)
	server = &http.Server{Addr: cfg.Listen, Handler: lh}

	if cfg.Certfile != "" && cfg.Keyfile != "" {
		// for higher score in ssllab
		server.TLSConfig = &tls.Config{
			MinVersion:               tls.VersionTLS12,
			PreferServerCipherSuites: true,
		}
		server.TLSConfig.GetCertificate = getCertificate
		log.Printf("TLS:%s, %s", cfg.Certfile, cfg.Keyfile)
		watchReloadCert()
		log.Fatal(server.ListenAndServeTLS(cfg.Certfile, cfg.Keyfile))
	}
	server.ListenAndServe()

}

func logTimeAndStatus(handler http.Handler) http.Handler {

	var (
		writer io.Writer
		err    error
	)

	if cfg.AccessLogPath != "" {
		var file *os.File
		file, err = os.OpenFile(cfg.AccessLogPath,
			os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatal(err)
		}
		writer = file
		log.Printf("Access Log to file: %s", cfg.AccessLogPath)
	} else {
		writer = os.Stdout
	}

	accessLogger := log.New(writer, "", log.LstdFlags)

	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		writer := logPool.Get().(*LogWriter)
		writer.ResponseWriter = w
		writer.statusCode = 200

		if cfg.Certfile != "" {
			writer.ResponseWriter.Header().Add("Strict-Transport-Security", "max-age=31536000")
		}
		handler.ServeHTTP(writer, r)

		delta := time.Now().Sub(start)
		accessLogger.Printf("%s %s %s %s %d",
			r.RemoteAddr, r.Method, r.URL.Path,
			delta, writer.statusCode)

		httpRequestDurationSeconds.Observe(delta.Seconds())
		httpRequestCount.Inc()

		logPool.Put(writer)
	})
}

type LogWriter struct {
	http.ResponseWriter
	statusCode int
}

func (l *LogWriter) WriteHeader(i int) {
	l.statusCode = i
	l.ResponseWriter.WriteHeader(i)
}

func loadCertificate() {

	log.Println("Loading new certs")
	cert, err := tls.LoadX509KeyPair(cfg.Certfile, cfg.Keyfile)
	if err != nil {
		log.Println("load cert failed keep old", err)
		return
	}
	tlsCert = &cert
}

func watchReloadCert() {
	go func() {
		c := make(chan os.Signal, 1)
		signal.Notify(c, syscall.SIGUSR1)
		for range c {
			log.Print("got reload cert signal")
			loadCertificate()
		}
	}()
}

func getCertificate(ch *tls.ClientHelloInfo) (cert *tls.Certificate, err error) {
	return tlsCert, nil
}

// Config ---------------------

var cfg *ServerConfig

type ServerConfig struct {
	Certfile      string
	Keyfile       string
	Listen        string
	MetricListen  string
	AccessLogPath string
}

func init() {
	cfg = &ServerConfig{
		"", "", ":8080",
		"access.log", ""}
}

func loadMetric(addr string) {

	prometheus.MustRegister(httpRequestCount)
	prometheus.MustRegister(httpRequestDurationSeconds)
	http.Handle("/metrics", promhttp.Handler())
	http.ListenAndServe(addr, nil)
}

var (
	httpRequestCount = prometheus.NewCounter(prometheus.CounterOpts{
		Namespace: "http",
		Subsystem: "request",
		Name:      "requests_count",
		Help:      "The total number of http request",
	})
	httpRequestDurationSeconds = prometheus.NewSummary(
		prometheus.SummaryOpts{
			Namespace: "http",
			Subsystem: "request",
			Name:      "duration_seconds",
			Help:      "The request duration distribution",
		})
)
<|endoftext|>


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

package main

import (
	"io/ioutil"
	"os"
	"strconv"
	"strings"

	"github.com/mitchellh/go-homedir"
)

const (
	defaultInstanceCount       = 3
	defaultGluonImage          = "pulcy/gluon:0.17.0"
	defaultRebootStrategy      = "etcd-lock"
	defaultMinOSVersion        = "835.13.0"
	defaultGithubTokenPathTmpl = "~/.pulcy/github-token"
)

func defaultDomain() string {
	return os.Getenv("QUARK_DOMAIN")
}

func defaultPrivateRegistryUrl() string {
	return os.Getenv("QUARK_REGISTRY_URL")
}

func defaultPrivateRegistryUserName() string {
	return os.Getenv("QUARK_REGISTRY_USERNAME")
}

func defaultPrivateRegistryPassword() string {
	return os.Getenv("QUARK_REGISTRY_PASSWORD")
}

func defaultSshKeys() []string {
	return []string{os.Getenv("QUARK_SSH_KEY")}
}

func defaultSshKeyGithubAccount() string {
	return os.Getenv("QUARK_SSH_KEY_GITHUB_ACCOUNT")
}

func defaultGithubToken() string {
	path, err := homedir.Expand(defaultGithubTokenPathTmpl)
	if err != nil {
		log.Warningf("Cannot expand %s: %#v", defaultGithubTokenPathTmpl, err)
		return ""
	}
	content, err := ioutil.ReadFile(path)
	if os.IsNotExist(err) {
		return ""
	} else if err != nil {
		log.Warningf("Cannot read %s: %#v", path, err)
		return ""
	}
	return strings.TrimSpace(string(content))
}

func defaultVagrantFolder() string {
	return os.Getenv("QUARK_VAGRANT_FOLDER")
}

func defaultVaultAddr() string {
	return os.Getenv("VAULT_ADDR")
}

func defaultVaultCACert() string {
	return os.Getenv("VAULT_CACERT")
}

func defaultRegisterInstance() bool {
	v := os.Getenv("QUARK_REGISTER_INSTANCES")
	register, err := strconv.ParseBool(v)
	return (err == nil) && register
}
<commit_msg>Updated gluon to 0.17.2<commit_after>// Copyright (c) 2016 Pulcy.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"io/ioutil"
	"os"
	"strconv"
	"strings"

	"github.com/mitchellh/go-homedir"
)

const (
	defaultInstanceCount       = 3
	defaultGluonImage          = "pulcy/gluon:0.17.2"
	defaultRebootStrategy      = "etcd-lock"
	defaultMinOSVersion        = "835.13.0"
	defaultGithubTokenPathTmpl = "~/.pulcy/github-token"
)

func defaultDomain() string {
	return os.Getenv("QUARK_DOMAIN")
}

func defaultPrivateRegistryUrl() string {
	return os.Getenv("QUARK_REGISTRY_URL")
}

func defaultPrivateRegistryUserName() string {
	return os.Getenv("QUARK_REGISTRY_USERNAME")
}

func defaultPrivateRegistryPassword() string {
	return os.Getenv("QUARK_REGISTRY_PASSWORD")
}

func defaultSshKeys() []string {
	return []string{os.Getenv("QUARK_SSH_KEY")}
}

func defaultSshKeyGithubAccount() string {
	return os.Getenv("QUARK_SSH_KEY_GITHUB_ACCOUNT")
}

func defaultGithubToken() string {
	path, err := homedir.Expand(defaultGithubTokenPathTmpl)
	if err != nil {
		log.Warningf("Cannot expand %s: %#v", defaultGithubTokenPathTmpl, err)
		return ""
	}
	content, err := ioutil.ReadFile(path)
	if os.IsNotExist(err) {
		return ""
	} else if err != nil {
		log.Warningf("Cannot read %s: %#v", path, err)
		return ""
	}
	return strings.TrimSpace(string(content))
}

func defaultVagrantFolder() string {
	return os.Getenv("QUARK_VAGRANT_FOLDER")
}

func defaultVaultAddr() string {
	return os.Getenv("VAULT_ADDR")
}

func defaultVaultCACert() string {
	return os.Getenv("VAULT_CACERT")
}

func defaultRegisterInstance() bool {
	v := os.Getenv("QUARK_REGISTER_INSTANCES")
	register, err := strconv.ParseBool(v)
	return (err == nil) && register
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"github.com/codegangsta/cli"
	"github.com/dockercn/docker-bucket/markdown"
	"io/ioutil"
	"log"
	"os"
	"strings"
)

var CmdDoc = cli.Command{
	Name:        "category",
	Usage:       "通过命令同步或者获取文档数据",
	Description: "通过命令将远程Git库中存放的markdown文件同步到本地，并且将markdown格式转换成html后存入ledis中;通过命令可以查询同步之后的目录",
	Action:      runCategory,
	Flags: []cli.Flag{
		cli.StringFlag{"action", "", "输入操作的类型[sync 远程同步数据到本地;render 将文件数据处理后加入到缓存;save 将数据存入数据库中;query 查询(输入doc的前缀值，可查询doc目录;如果查询具体文件，请使用permalink参数)]", ""},
		cli.StringFlag{"remote", "", "输入同步远程库的地址,例如：https://github.com/xxx/xxx.git", ""},
		cli.StringFlag{"local", "", "输入远程库同步到本地的路径,例如：/root/data", ""},
		cli.StringFlag{"prefix", "", "输入同步类型的前缀名", ""},
		cli.StringFlag{"permalink", "", "输入查询文件的permalink值,例如：文件名xxx.md 则permalink值为xxx", ""},
	},
}

func runCategory(c *cli.Context) {
	action := strings.TrimSpace(c.String("action"))
	if len(action) == 0 {
		log.Fatalln(errors.New("文档操作请输入action的值"))
		return
	}
	category := &markdown.Category{
		Remote: strings.TrimSpace(c.String("remote")),
		Local:  strings.TrimSpace(c.String("local")),
		Prefix: strings.TrimSpace(c.String("prefix")),
	}
	switch action {
	case "sync":
		if err := validate(category, "sync"); err != nil {
			log.Fatalln(err)
			return
		} else if err := category.Sync(); err != nil {
			log.Fatalln(err)
			return
		}
	case "render":
		if err := validate(category, "render"); err != nil {
			log.Fatalln(err)
			return
		} else if err := category.Render(); err != nil {
			log.Fatalln(err)
			return
		}
	case "save":
		if err := validate(category, "save"); err != nil {
			log.Fatalln(err)
			return
		} else if err := category.Save(); err != nil {
			log.Fatalln(err)
			return
		}
	case "query":
		if err := validate(category, "query"); err != nil {
			log.Fatalln(err)
			return
		}
		if len(strings.TrimSpace(c.String("permalink"))) == 0 {
			if len(strings.TrimSpace(c.String("prefix"))) == 0 {
				log.Fatalln(errors.New("请输入prefix的值"))
				return
			} else if docs, err := category.Query(true); err != nil {
				log.Fatalln(err)
				return
			} else {
				log.Println(docs)
			}
		} else if docs, err := category.Query(false, strings.TrimSpace(c.String("permalink"))); err != nil {
			log.Fatalln(err)
			return
		} else {
			log.Println(docs[0])
		}
	default:
		log.Fatalln(errors.New("输入的action参数不存在"))
	}
}

func validate(category *markdown.Category, action string) error {
	switch action {
	case "sync":
		if len(strings.TrimSpace(category.Remote)) == 0 || len(strings.TrimSpace(category.Local)) == 0 {
			return errors.New("markdown git地址初始化异常,请赋值remote和local")
		}
	case "render":
		if len(strings.TrimSpace(category.Local)) == 0 {
			return errors.New("请输入local的值")
		} else if files, _ := ioutil.ReadDir(category.Local); len(files) == 0 {
			return errors.New("本地路径不存在文件,无法进行转换处理，请执行sync操作,确认文件已经同步")
		}
	case "save":
		if _, err := os.Stat(".render"); err != nil || len(strings.TrimSpace(category.Prefix)) == 0 {
			return errors.New("请确认是否值之前执行了sync、render的操作;检查prefix的值")
		}
	}
	return nil
}
<commit_msg>删除掉markdown功能<commit_after><|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/dustin/go-humanize"

	"github.intel.com/hpdd/ce-tools/pkg/applog"
	"github.intel.com/hpdd/lustre"
	"github.intel.com/hpdd/lustre/fs"
	"github.intel.com/hpdd/lustre/hsm"
)

func init() {
	hsmStateFlags := strings.Join(hsm.GetStateFlagNames(), ",")

	hsmCommand := cli.Command{
		Name:  "hsm",
		Usage: "HSM-related data movement actions",
		Subcommands: []cli.Command{
			{
				Name:      "archive",
				Usage:     "Initiate HSM archive of specified paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.IntFlag{
						Name:  "id, i",
						Usage: "Numeric ID of archive backend",
					},
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmAction,
			},
			{
				Name:      "release",
				Usage:     "Release local data of HSM-archived paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmAction,
			},
			{
				Name:      "restore",
				Usage:     "Explicitly restore local data of HSM-archived paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmAction,
			},
			{
				Name:      "remove",
				Usage:     "Remove HSM-archived data of specified paths (local data is not removed)",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmAction,
			},
			{
				Name:      "cancel",
				Usage:     "Cancel HSM operations being performed on specified paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmAction,
			},
			{
				Name:      "set",
				Usage:     "Set HSM flags or archive ID for specified paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
					cli.IntFlag{
						Name:  "id, i",
						Usage: "Numeric ID of archive backend",
					},
					cli.StringSliceFlag{
						Name:  "flag, f",
						Usage: fmt.Sprintf("HSM flag to set (%s)", hsmStateFlags),
						Value: &cli.StringSlice{},
					},
					cli.StringSliceFlag{
						Name:  "clear, F",
						Usage: fmt.Sprintf("HSM flag to clear (%s)", hsmStateFlags),
						Value: &cli.StringSlice{},
					},
				},
				Action: hsmSetAction,
			},
			{
				Name:      "status",
				Usage:     "Display HSM status for specified paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "action, a",
						Usage: "Include current HSM action",
					},
					cli.BoolFlag{
						Name:  "hide-path, H",
						Usage: "Hide pathname in output",
					},
					cli.BoolFlag{
						Name:  "long, l",
						Usage: "Show long-form states",
					},
					cli.BoolFlag{
						Name:  "progress, p",
						Usage: "Show copy progress for archive/restore actions",
					},
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmStatusAction,
			},
		},
	}
	commands = append(commands, hsmCommand)
}

func getFilePaths(c *cli.Context) ([]string, error) {
	var paths []string

	if c.Bool("null") {
		reader := bufio.NewReader(os.Stdin)
		path, err := reader.ReadBytes('\000')
		for err == nil {
			paths = append(paths, string(path[:len(path)-1]))
			path, err = reader.ReadBytes('\000')
		}
		if err != io.EOF {
			return nil, err
		}
	} else {
		paths = c.Args()
	}

	return paths, nil
}

func getPathStatus(c *cli.Context, filePath string) (string, error) {
	var buf bytes.Buffer

	s, err := hsm.GetFileStatus(filePath)
	if err != nil {
		return "", err
	}

	if !c.Bool("hide-path") {
		fmt.Fprintf(&buf, "%s ", filePath)
	}
	fmt.Fprintf(&buf, hsm.FileStatusString(s, !c.Bool("long")))

	if s.Exists() && c.Bool("action") {
		a, err := hsm.GetFileAction(filePath)
		if err != nil {
			return "", err
		}
		if a.IsNone() {
			fmt.Fprintf(&buf, " -")
		} else {
			fmt.Fprintf(&buf, " [%s:%s]", a.Action(), a.State())
			if c.Bool("progress") && (a.IsArchive() || a.IsRestore()) {
				st, err := os.Stat(filePath)
				if err != nil {
					applog.Fail(err)
				}
				fmt.Fprintf(&buf, "(%s/%s)",
					humanize.IBytes(a.BytesCopied),
					humanize.IBytes(uint64(st.Size())))
			}
		}
	} else {
		fmt.Fprintf(&buf, " -")
	}

	// TODO: Display xattrs, once we've standardized them?

	return buf.String(), nil
}

func hsmSetAction(c *cli.Context) {
	paths, err := getFilePaths(c)
	if err != nil {
		applog.Fail(err)
	}

	if len(paths) < 1 {
		applog.Fail(fmt.Errorf("HSM set request must be made with at least 1 path"))
	}

	setFlags, err := hsm.GetStatusMask(c.StringSlice("flag"))
	if err != nil {
		applog.Fail(err)
	}
	clearFlags, err := hsm.GetStatusMask(c.StringSlice("clear"))
	if err != nil {
		applog.Fail(err)
	}
	archiveID := uint32(c.Int("id"))

	if setFlags == 0 && clearFlags == 0 && archiveID == 0 {
		applog.Fail(fmt.Errorf("HSM set request made with no flags to set or clear, and no new archive ID supplied"))
	}

	// TODO: Parallelize this?
	for _, path := range paths {
		if err := hsm.SetFileStatus(path, setFlags, clearFlags, archiveID); err != nil {
			applog.Fail(err)
		}
	}
}

func hsmStatusAction(c *cli.Context) {
	paths, err := getFilePaths(c)
	if err != nil {
		applog.Fail(err)
	}

	if len(paths) < 1 {
		applog.Fail(fmt.Errorf("HSM status request must be made with at least 1 path"))
	}

	for _, path := range paths {
		status, err := getPathStatus(c, path)
		if err != nil {
			applog.Fail(err)
		}
		fmt.Println(status)
	}
}

func hsmAction(c *cli.Context) {
	paths, err := getFilePaths(c)
	if err != nil {
		applog.Fail(err)
	}

	if err := submitHsmRequest(c.Command.Name, uint(c.Int("id")), paths...); err != nil {
		applog.Fail(err)
	}
}

func submitHsmRequest(actionName string, archiveID uint, paths ...string) error {
	var fids []*lustre.Fid

	if len(paths) < 1 {
		return fmt.Errorf("HSM %s request must be made with at least 1 path", actionName)
	}

	fsID, err := fs.GetID(paths[0])
	if err != nil {
		return fmt.Errorf("Error getting fs ID from %s: %s", paths[0], err)
	}

	fsRoot, err := fs.MountRoot(paths[0])
	if err != nil {
		return fmt.Errorf("Error getting fs root from %s: %s", paths[0], err)
	}

	// TODO: Occurs to me that it might be better to break up a large
	// batch into multiple batches, each serviced by its own goroutine.
	for _, path := range paths {
		absPath, err := filepath.Abs(path)
		if !strings.HasPrefix(absPath, string(fsRoot)) {
			return fmt.Errorf("All files in HSM request must be in the same filesystem (%s is not in %s)", path, fsRoot)
		}

		fid, err := fs.LookupFid(path)
		if err != nil {
			return fmt.Errorf("Cannot resolve Fid for %s: %s", path, err)
		}
		fids = append(fids, fid)
	}

	switch actionName {
	case "archive":
		err = hsm.RequestArchive(fsID, archiveID, fids)
	case "release":
		err = hsm.RequestRelease(fsID, archiveID, fids)
	case "restore":
		err = hsm.RequestRestore(fsID, archiveID, fids)
	case "remove":
		err = hsm.RequestRemove(fsID, archiveID, fids)
	case "cancel":
		err = hsm.RequestCancel(fsID, archiveID, fids)
	default:
		err = fmt.Errorf("Unhandled HSM action: %s", actionName)
	}

	return err
}
<commit_msg>Pass RootDir to hsm functions instead of ID.<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/dustin/go-humanize"

	"github.intel.com/hpdd/ce-tools/pkg/applog"
	"github.intel.com/hpdd/lustre"
	"github.intel.com/hpdd/lustre/fs"
	"github.intel.com/hpdd/lustre/hsm"
)

func init() {
	hsmStateFlags := strings.Join(hsm.GetStateFlagNames(), ",")

	hsmCommand := cli.Command{
		Name:  "hsm",
		Usage: "HSM-related data movement actions",
		Subcommands: []cli.Command{
			{
				Name:      "archive",
				Usage:     "Initiate HSM archive of specified paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.IntFlag{
						Name:  "id, i",
						Usage: "Numeric ID of archive backend",
					},
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmAction,
			},
			{
				Name:      "release",
				Usage:     "Release local data of HSM-archived paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmAction,
			},
			{
				Name:      "restore",
				Usage:     "Explicitly restore local data of HSM-archived paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmAction,
			},
			{
				Name:      "remove",
				Usage:     "Remove HSM-archived data of specified paths (local data is not removed)",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmAction,
			},
			{
				Name:      "cancel",
				Usage:     "Cancel HSM operations being performed on specified paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmAction,
			},
			{
				Name:      "set",
				Usage:     "Set HSM flags or archive ID for specified paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
					cli.IntFlag{
						Name:  "id, i",
						Usage: "Numeric ID of archive backend",
					},
					cli.StringSliceFlag{
						Name:  "flag, f",
						Usage: fmt.Sprintf("HSM flag to set (%s)", hsmStateFlags),
						Value: &cli.StringSlice{},
					},
					cli.StringSliceFlag{
						Name:  "clear, F",
						Usage: fmt.Sprintf("HSM flag to clear (%s)", hsmStateFlags),
						Value: &cli.StringSlice{},
					},
				},
				Action: hsmSetAction,
			},
			{
				Name:      "status",
				Usage:     "Display HSM status for specified paths",
				ArgsUsage: "[path [path...]]",
				Flags: []cli.Flag{
					cli.BoolFlag{
						Name:  "action, a",
						Usage: "Include current HSM action",
					},
					cli.BoolFlag{
						Name:  "hide-path, H",
						Usage: "Hide pathname in output",
					},
					cli.BoolFlag{
						Name:  "long, l",
						Usage: "Show long-form states",
					},
					cli.BoolFlag{
						Name:  "progress, p",
						Usage: "Show copy progress for archive/restore actions",
					},
					cli.BoolFlag{
						Name:  "null, 0",
						Usage: "Null-separated paths are read from stdin (e.g. piped from find -print0)",
					},
				},
				Action: hsmStatusAction,
			},
		},
	}
	commands = append(commands, hsmCommand)
}

func getFilePaths(c *cli.Context) ([]string, error) {
	var paths []string

	if c.Bool("null") {
		reader := bufio.NewReader(os.Stdin)
		path, err := reader.ReadBytes('\000')
		for err == nil {
			paths = append(paths, string(path[:len(path)-1]))
			path, err = reader.ReadBytes('\000')
		}
		if err != io.EOF {
			return nil, err
		}
	} else {
		paths = c.Args()
	}

	return paths, nil
}

func getPathStatus(c *cli.Context, filePath string) (string, error) {
	var buf bytes.Buffer

	s, err := hsm.GetFileStatus(filePath)
	if err != nil {
		return "", err
	}

	if !c.Bool("hide-path") {
		fmt.Fprintf(&buf, "%s ", filePath)
	}
	fmt.Fprintf(&buf, hsm.FileStatusString(s, !c.Bool("long")))

	if s.Exists() && c.Bool("action") {
		a, err := hsm.GetFileAction(filePath)
		if err != nil {
			return "", err
		}
		if a.IsNone() {
			fmt.Fprintf(&buf, " -")
		} else {
			fmt.Fprintf(&buf, " [%s:%s]", a.Action(), a.State())
			if c.Bool("progress") && (a.IsArchive() || a.IsRestore()) {
				st, err := os.Stat(filePath)
				if err != nil {
					applog.Fail(err)
				}
				fmt.Fprintf(&buf, "(%s/%s)",
					humanize.IBytes(a.BytesCopied),
					humanize.IBytes(uint64(st.Size())))
			}
		}
	} else {
		fmt.Fprintf(&buf, " -")
	}

	// TODO: Display xattrs, once we've standardized them?

	return buf.String(), nil
}

func hsmSetAction(c *cli.Context) {
	paths, err := getFilePaths(c)
	if err != nil {
		applog.Fail(err)
	}

	if len(paths) < 1 {
		applog.Fail(fmt.Errorf("HSM set request must be made with at least 1 path"))
	}

	setFlags, err := hsm.GetStatusMask(c.StringSlice("flag"))
	if err != nil {
		applog.Fail(err)
	}
	clearFlags, err := hsm.GetStatusMask(c.StringSlice("clear"))
	if err != nil {
		applog.Fail(err)
	}
	archiveID := uint32(c.Int("id"))

	if setFlags == 0 && clearFlags == 0 && archiveID == 0 {
		applog.Fail(fmt.Errorf("HSM set request made with no flags to set or clear, and no new archive ID supplied"))
	}

	// TODO: Parallelize this?
	for _, path := range paths {
		if err := hsm.SetFileStatus(path, setFlags, clearFlags, archiveID); err != nil {
			applog.Fail(err)
		}
	}
}

func hsmStatusAction(c *cli.Context) {
	paths, err := getFilePaths(c)
	if err != nil {
		applog.Fail(err)
	}

	if len(paths) < 1 {
		applog.Fail(fmt.Errorf("HSM status request must be made with at least 1 path"))
	}

	for _, path := range paths {
		status, err := getPathStatus(c, path)
		if err != nil {
			applog.Fail("%s: %v", path, err)
		}
		fmt.Println(status)
	}
}

func hsmAction(c *cli.Context) {
	paths, err := getFilePaths(c)
	if err != nil {
		applog.Fail(err)
	}

	if err := submitHsmRequest(c.Command.Name, uint(c.Int("id")), paths...); err != nil {
		applog.Fail(err)
	}
}

func submitHsmRequest(actionName string, archiveID uint, paths ...string) error {
	var fids []*lustre.Fid

	if len(paths) < 1 {
		return fmt.Errorf("HSM %s request must be made with at least 1 path", actionName)
	}

	fsRoot, err := fs.MountRoot(paths[0])
	if err != nil {
		return fmt.Errorf("Error getting fs root from %s: %s", paths[0], err)
	}

	// TODO: Occurs to me that it might be better to break up a large
	// batch into multiple batches, each serviced by its own goroutine.
	for _, path := range paths {
		absPath, err := filepath.Abs(path)
		if !strings.HasPrefix(absPath, string(fsRoot)) {
			return fmt.Errorf("All files in HSM request must be in the same filesystem (%s is not in %s)", path, fsRoot)
		}

		fid, err := fs.LookupFid(path)
		if err != nil {
			return fmt.Errorf("Cannot resolve Fid for %s: %s", path, err)
		}
		fids = append(fids, fid)
	}

	switch actionName {
	case "archive":
		err = hsm.RequestArchive(fsRoot, archiveID, fids)
	case "release":
		err = hsm.RequestRelease(fsRoot, archiveID, fids)
	case "restore":
		err = hsm.RequestRestore(fsRoot, archiveID, fids)
	case "remove":
		err = hsm.RequestRemove(fsRoot, archiveID, fids)
	case "cancel":
		err = hsm.RequestCancel(fsRoot, archiveID, fids)
	default:
		err = fmt.Errorf("Unhandled HSM action: %s", actionName)
	}

	return err
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/cloudfoundry-incubator/auction/communication/http/auction_http_handlers"
	auctionroutes "github.com/cloudfoundry-incubator/auction/communication/http/routes"
	"github.com/cloudfoundry-incubator/cf-debug-server"
	"github.com/cloudfoundry-incubator/cf-lager"
	"github.com/cloudfoundry-incubator/executor"
	executorclient "github.com/cloudfoundry-incubator/executor/http/client"
	"github.com/cloudfoundry-incubator/rep/auction_cell_rep"
	"github.com/cloudfoundry-incubator/rep/generator"
	"github.com/cloudfoundry-incubator/rep/harmonizer"
	repserver "github.com/cloudfoundry-incubator/rep/http_server"
	"github.com/cloudfoundry-incubator/rep/lrp_stopper"
	"github.com/cloudfoundry-incubator/rep/maintain"
	Bbs "github.com/cloudfoundry-incubator/runtime-schema/bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/bbs/lock_bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	bbsroutes "github.com/cloudfoundry-incubator/runtime-schema/routes"
	"github.com/cloudfoundry/dropsonde"
	"github.com/cloudfoundry/gunk/timeprovider"
	"github.com/cloudfoundry/gunk/workpool"
	"github.com/cloudfoundry/storeadapter/etcdstoreadapter"
	"github.com/nu7hatch/gouuid"
	"github.com/pivotal-golang/lager"
	"github.com/pivotal-golang/localip"
	"github.com/pivotal-golang/operationq"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/grouper"
	"github.com/tedsuo/ifrit/http_server"
	"github.com/tedsuo/ifrit/sigmon"
	"github.com/tedsuo/rata"
)

var etcdCluster = flag.String(
	"etcdCluster",
	"http://127.0.0.1:4001",
	"comma-separated list of etcd addresses (http://ip:port)",
)

var heartbeatInterval = flag.Duration(
	"heartbeatInterval",
	lock_bbs.HEARTBEAT_INTERVAL,
	"the interval between heartbeats for maintaining presence",
)

var executorURL = flag.String(
	"executorURL",
	"http://127.0.0.1:1700",
	"location of executor to represent",
)

var listenAddr = flag.String(
	"listenAddr",
	"0.0.0.0:1800",
	"host:port to serve auction and LRP stop requests on",
)

var stack = flag.String(
	"stack",
	"",
	"the rep stack - must be specified",
)

var cellID = flag.String(
	"cellID",
	"",
	"the ID used by the rep to identify itself to external systems - must be specified",
)

var zone = flag.String(
	"zone",
	"",
	"the availability zone associated with the rep",
)

var pollingInterval = flag.Duration(
	"pollingInterval",
	30*time.Second,
	"the interval on which to scan the executor",
)

const (
	dropsondeDestination = "localhost:3457"
	dropsondeOrigin      = "rep"
)

func main() {
	cf_debug_server.AddFlags(flag.CommandLine)
	flag.Parse()

	logger := cf_lager.New("rep")
	initializeDropsonde(logger)

	if *cellID == "" {
		log.Fatalf("-cellID must be specified")
	}

	if *stack == "" {
		log.Fatalf("-stack must be specified")
	}

	bbs := initializeRepBBS(logger)
	executorClient := executorclient.New(http.DefaultClient, *executorURL)
	server, address := initializeServer(bbs, executorClient, logger)
	opGenerator := generator.New(*cellID, bbs, executorClient)

	// only one outstanding operation per container is necessary
	queue := operationq.NewSlidingQueue(1)

	members := grouper.Members{
		{"server", server},
		{"heartbeater", initializeCellHeartbeat(address, bbs, executorClient, logger)},
		{"bulker", harmonizer.NewBulker(logger, *pollingInterval, timeprovider.NewTimeProvider(), opGenerator, queue)},
		{"event-consumer", harmonizer.NewEventConsumer(logger, opGenerator, queue)},
	}

	if dbgAddr := cf_debug_server.DebugAddress(flag.CommandLine); dbgAddr != "" {
		members = append(grouper.Members{
			{"debug-server", cf_debug_server.Runner(dbgAddr)},
		}, members...)
	}

	group := grouper.NewOrdered(os.Interrupt, members)

	monitor := ifrit.Invoke(sigmon.New(group))

	logger.Info("started", lager.Data{"cell-id": *cellID})

	<-monitor.Wait()
	logger.Info("shutting-down")
}

func initializeDropsonde(logger lager.Logger) {
	err := dropsonde.Initialize(dropsondeDestination, dropsondeOrigin)
	if err != nil {
		logger.Error("failed to initialize dropsonde: %v", err)
	}
}

func initializeCellHeartbeat(address string, bbs Bbs.RepBBS, executorClient executor.Client, logger lager.Logger) ifrit.Runner {
	cellPresence := models.CellPresence{
		CellID:     *cellID,
		RepAddress: address,
		Stack:      *stack,
		Zone:       *zone,
	}

	heartbeat := bbs.NewCellHeartbeat(cellPresence, *heartbeatInterval)
	return maintain.New(executorClient, heartbeat, logger, *heartbeatInterval, timeprovider.NewTimeProvider())
}

func initializeRepBBS(logger lager.Logger) Bbs.RepBBS {
	etcdAdapter := etcdstoreadapter.NewETCDStoreAdapter(
		strings.Split(*etcdCluster, ","),
		workpool.NewWorkPool(10),
	)

	err := etcdAdapter.Connect()
	if err != nil {
		logger.Fatal("failed-to-connect-to-etcd", err)
	}

	return Bbs.NewRepBBS(etcdAdapter, timeprovider.NewTimeProvider(), logger)
}

func initializeLRPStopper(guid string, executorClient executor.Client, logger lager.Logger) lrp_stopper.LRPStopper {
	return lrp_stopper.New(guid, executorClient, logger)
}

func initializeServer(
	bbs Bbs.RepBBS,
	executorClient executor.Client,
	logger lager.Logger,
) (ifrit.Runner, string) {
	lrpStopper := initializeLRPStopper(*cellID, executorClient, logger)

	auctionCellRep := auction_cell_rep.New(*cellID, *stack, *zone, generateGuid, bbs, executorClient, logger)
	handlers := auction_http_handlers.New(auctionCellRep, logger)

	handlers[bbsroutes.StopLRPInstance] = repserver.NewStopLRPInstanceHandler(logger, lrpStopper)
	routes := append(auctionroutes.Routes, bbsroutes.StopLRPRoutes...)

	router, err := rata.NewRouter(routes, handlers)
	if err != nil {
		logger.Fatal("failed-to-construct-router", err)
	}

	ip, err := localip.LocalIP()
	if err != nil {
		logger.Fatal("failed-to-fetch-ip", err)
	}

	port := strings.Split(*listenAddr, ":")[1]
	address := fmt.Sprintf("http://%s:%s", ip, port)

	return http_server.New(*listenAddr, router), address
}

func generateGuid() (string, error) {
	guid, err := uuid.NewV4()
	if err != nil {
		return "", err
	}
	return guid.String(), nil
}
<commit_msg>explicitly add cf_lager flags<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/cloudfoundry-incubator/auction/communication/http/auction_http_handlers"
	auctionroutes "github.com/cloudfoundry-incubator/auction/communication/http/routes"
	"github.com/cloudfoundry-incubator/cf-debug-server"
	"github.com/cloudfoundry-incubator/cf-lager"
	"github.com/cloudfoundry-incubator/executor"
	executorclient "github.com/cloudfoundry-incubator/executor/http/client"
	"github.com/cloudfoundry-incubator/rep/auction_cell_rep"
	"github.com/cloudfoundry-incubator/rep/generator"
	"github.com/cloudfoundry-incubator/rep/harmonizer"
	repserver "github.com/cloudfoundry-incubator/rep/http_server"
	"github.com/cloudfoundry-incubator/rep/lrp_stopper"
	"github.com/cloudfoundry-incubator/rep/maintain"
	Bbs "github.com/cloudfoundry-incubator/runtime-schema/bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/bbs/lock_bbs"
	"github.com/cloudfoundry-incubator/runtime-schema/models"
	bbsroutes "github.com/cloudfoundry-incubator/runtime-schema/routes"
	"github.com/cloudfoundry/dropsonde"
	"github.com/cloudfoundry/gunk/timeprovider"
	"github.com/cloudfoundry/gunk/workpool"
	"github.com/cloudfoundry/storeadapter/etcdstoreadapter"
	"github.com/nu7hatch/gouuid"
	"github.com/pivotal-golang/lager"
	"github.com/pivotal-golang/localip"
	"github.com/pivotal-golang/operationq"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/grouper"
	"github.com/tedsuo/ifrit/http_server"
	"github.com/tedsuo/ifrit/sigmon"
	"github.com/tedsuo/rata"
)

var etcdCluster = flag.String(
	"etcdCluster",
	"http://127.0.0.1:4001",
	"comma-separated list of etcd addresses (http://ip:port)",
)

var heartbeatInterval = flag.Duration(
	"heartbeatInterval",
	lock_bbs.HEARTBEAT_INTERVAL,
	"the interval between heartbeats for maintaining presence",
)

var executorURL = flag.String(
	"executorURL",
	"http://127.0.0.1:1700",
	"location of executor to represent",
)

var listenAddr = flag.String(
	"listenAddr",
	"0.0.0.0:1800",
	"host:port to serve auction and LRP stop requests on",
)

var stack = flag.String(
	"stack",
	"",
	"the rep stack - must be specified",
)

var cellID = flag.String(
	"cellID",
	"",
	"the ID used by the rep to identify itself to external systems - must be specified",
)

var zone = flag.String(
	"zone",
	"",
	"the availability zone associated with the rep",
)

var pollingInterval = flag.Duration(
	"pollingInterval",
	30*time.Second,
	"the interval on which to scan the executor",
)

const (
	dropsondeDestination = "localhost:3457"
	dropsondeOrigin      = "rep"
)

func main() {
	cf_debug_server.AddFlags(flag.CommandLine)
	cf_lager.AddFlags(flag.CommandLine)
	flag.Parse()

	logger := cf_lager.New("rep")
	initializeDropsonde(logger)

	if *cellID == "" {
		log.Fatalf("-cellID must be specified")
	}

	if *stack == "" {
		log.Fatalf("-stack must be specified")
	}

	bbs := initializeRepBBS(logger)
	executorClient := executorclient.New(http.DefaultClient, *executorURL)
	server, address := initializeServer(bbs, executorClient, logger)
	opGenerator := generator.New(*cellID, bbs, executorClient)

	// only one outstanding operation per container is necessary
	queue := operationq.NewSlidingQueue(1)

	members := grouper.Members{
		{"server", server},
		{"heartbeater", initializeCellHeartbeat(address, bbs, executorClient, logger)},
		{"bulker", harmonizer.NewBulker(logger, *pollingInterval, timeprovider.NewTimeProvider(), opGenerator, queue)},
		{"event-consumer", harmonizer.NewEventConsumer(logger, opGenerator, queue)},
	}

	if dbgAddr := cf_debug_server.DebugAddress(flag.CommandLine); dbgAddr != "" {
		members = append(grouper.Members{
			{"debug-server", cf_debug_server.Runner(dbgAddr)},
		}, members...)
	}

	group := grouper.NewOrdered(os.Interrupt, members)

	monitor := ifrit.Invoke(sigmon.New(group))

	logger.Info("started", lager.Data{"cell-id": *cellID})

	<-monitor.Wait()
	logger.Info("shutting-down")
}

func initializeDropsonde(logger lager.Logger) {
	err := dropsonde.Initialize(dropsondeDestination, dropsondeOrigin)
	if err != nil {
		logger.Error("failed to initialize dropsonde: %v", err)
	}
}

func initializeCellHeartbeat(address string, bbs Bbs.RepBBS, executorClient executor.Client, logger lager.Logger) ifrit.Runner {
	cellPresence := models.CellPresence{
		CellID:     *cellID,
		RepAddress: address,
		Stack:      *stack,
		Zone:       *zone,
	}

	heartbeat := bbs.NewCellHeartbeat(cellPresence, *heartbeatInterval)
	return maintain.New(executorClient, heartbeat, logger, *heartbeatInterval, timeprovider.NewTimeProvider())
}

func initializeRepBBS(logger lager.Logger) Bbs.RepBBS {
	etcdAdapter := etcdstoreadapter.NewETCDStoreAdapter(
		strings.Split(*etcdCluster, ","),
		workpool.NewWorkPool(10),
	)

	err := etcdAdapter.Connect()
	if err != nil {
		logger.Fatal("failed-to-connect-to-etcd", err)
	}

	return Bbs.NewRepBBS(etcdAdapter, timeprovider.NewTimeProvider(), logger)
}

func initializeLRPStopper(guid string, executorClient executor.Client, logger lager.Logger) lrp_stopper.LRPStopper {
	return lrp_stopper.New(guid, executorClient, logger)
}

func initializeServer(
	bbs Bbs.RepBBS,
	executorClient executor.Client,
	logger lager.Logger,
) (ifrit.Runner, string) {
	lrpStopper := initializeLRPStopper(*cellID, executorClient, logger)

	auctionCellRep := auction_cell_rep.New(*cellID, *stack, *zone, generateGuid, bbs, executorClient, logger)
	handlers := auction_http_handlers.New(auctionCellRep, logger)

	handlers[bbsroutes.StopLRPInstance] = repserver.NewStopLRPInstanceHandler(logger, lrpStopper)
	routes := append(auctionroutes.Routes, bbsroutes.StopLRPRoutes...)

	router, err := rata.NewRouter(routes, handlers)
	if err != nil {
		logger.Fatal("failed-to-construct-router", err)
	}

	ip, err := localip.LocalIP()
	if err != nil {
		logger.Fatal("failed-to-fetch-ip", err)
	}

	port := strings.Split(*listenAddr, ":")[1]
	address := fmt.Sprintf("http://%s:%s", ip, port)

	return http_server.New(*listenAddr, router), address
}

func generateGuid() (string, error) {
	guid, err := uuid.NewV4()
	if err != nil {
		return "", err
	}
	return guid.String(), nil
}
<|endoftext|>


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

package present

import (
	"bytes"
	"fmt"
	"io"

	"github.com/spacemonkeygo/monkit/v3"
)

func formatDist(data *monkit.DurationDist, indent string) (result string) {
	for _, q := range monkit.ObservedQuantiles {
		result += fmt.Sprintf("%s%.02f: %s\n", indent, q, data.Query(q))
	}
	result += fmt.Sprintf("%savg: %s\n", indent, data.FullAverage())
	result += fmt.Sprintf("%sravg: %s\n", indent, data.ReservoirAverage())
	return result
}

// FuncsDot finds all of the Funcs known by Registry r and writes information
// about them in the dot graphics file format to w.
func FuncsDot(r *monkit.Registry, w io.Writer) (err error) {
	_, err = fmt.Fprintf(w, "digraph G {\n node [shape=box];\n")
	if err != nil {
		return err
	}
	r.Funcs(func(f *monkit.Func) {
		if err != nil {
			return
		}
		success := f.Success()
		panics := f.Panics()

		var err_out bytes.Buffer
		total_errors := int64(0)
		for errname, count := range f.Errors() {
			_, err = fmt.Fprint(&err_out, escapeDotLabel("error %s: %d\n", errname,
				count))
			if err != nil {
				return
			}
			total_errors += count
		}

		_, err = fmt.Fprintf(w, " f%d [label=\"%s", f.Id(),
			escapeDotLabel("%s\ncurrent: %d, highwater: %d, success: %d, "+
				"errors: %d, panics: %d\n", f.FullName(), f.Current(), f.Highwater(),
				success, total_errors, panics))
		if err != nil {
			return
		}

		_, err = err_out.WriteTo(w)
		if err != nil {
			return
		}

		if success > 0 {
			_, err = fmt.Fprint(w, escapeDotLabel(
				"success times:\n%s", formatDist(f.SuccessTimes(), "        ")))
			if err != nil {
				return
			}
		}

		if total_errors+panics > 0 {
			_, err = fmt.Fprint(w, escapeDotLabel(
				"failure times:\n%s", formatDist(f.FailureTimes(), "        ")))
			if err != nil {
				return
			}
		}

		_, err = fmt.Fprint(w, "\"];\n")
		if err != nil {
			return
		}

		f.Parents(func(parent *monkit.Func) {
			if err != nil {
				return
			}
			if parent != nil {
				_, err = fmt.Fprintf(w, " f%d -> f%d;\n", parent.Id(), f.Id())
				if err != nil {
					return
				}
			} else {
				_, err = fmt.Fprintf(w, " r%d [label=\"entry\"];\n r%d -> f%d;\n",
					f.Id(), f.Id(), f.Id())
				if err != nil {
					return
				}
			}
		})
	})
	if err != nil {
		return err
	}
	_, err = fmt.Fprintf(w, "}\n")
	return err
}

// FuncsText finds all of the Funcs known by Registry r and writes information
// about them in a plain text format to w.
func FuncsText(r *monkit.Registry, w io.Writer) (err error) {
	r.Funcs(func(f *monkit.Func) {
		if err != nil {
			return
		}
		_, err = fmt.Fprintf(w, "[%d] %s\n  parents: ", f.Id(), f.FullName())
		if err != nil {
			return
		}
		printed := false
		f.Parents(func(parent *monkit.Func) {
			if err != nil {
				return
			}
			if printed {
				_, err = fmt.Fprint(w, ", ")
				if err != nil {
					return
				}
			} else {
				printed = true
			}
			if parent != nil {
				_, err = fmt.Fprintf(w, "%d", parent.Id())
				if err != nil {
					return
				}
			} else {
				_, err = fmt.Fprintf(w, "entry")
				if err != nil {
					return
				}
			}
		})
		var err_out bytes.Buffer
		total_errors := int64(0)
		for errname, count := range f.Errors() {
			_, err = fmt.Fprintf(&err_out, "  error %s: %d\n", errname, count)
			if err != nil {
				return
			}
			total_errors += count
		}
		_, err = fmt.Fprintf(w,
			"\n  current: %d, highwater: %d, success: %d, errors: %d, panics: %d\n",
			f.Current(), f.Highwater(), f.Success(), total_errors, f.Panics())
		if err != nil {
			return
		}
		_, err = err_out.WriteTo(w)
		if err != nil {
			return
		}
		_, err = fmt.Fprintf(w, "  success times:\n%s  failure times:\n%s\n",
			formatDist(f.SuccessTimes(), "    "),
			formatDist(f.FailureTimes(), "    "))
	})
	return err
}

// FuncsJSON finds all of the Funcs known by Registry r and writes information
// about them in the JSON format to w.
func FuncsJSON(r *monkit.Registry, w io.Writer) (err error) {
	lw := newListWriter(w)
	r.Funcs(func(f *monkit.Func) {
		lw.elem(formatFunc(f))
	})
	return lw.done()
}
<commit_msg>include more info in func output<commit_after>// Copyright (C) 2015 Space Monkey, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package present

import (
	"bytes"
	"fmt"
	"io"

	"github.com/spacemonkeygo/monkit/v3"
)

func formatDist(data *monkit.DurationDist, indent string) (result string) {
	for _, q := range monkit.ObservedQuantiles {
		result += fmt.Sprintf("%s%.02f: %s\n", indent, q, data.Query(q))
	}
	result += fmt.Sprintf("%savg: %s\n", indent, data.FullAverage())
	result += fmt.Sprintf("%sravg: %s\n", indent, data.ReservoirAverage())
	result += fmt.Sprintf("%srecent: %s\n", indent, data.Recent)
	result += fmt.Sprintf("%ssum: %s\n", indent, data.Sum)
	return result
}

// FuncsDot finds all of the Funcs known by Registry r and writes information
// about them in the dot graphics file format to w.
func FuncsDot(r *monkit.Registry, w io.Writer) (err error) {
	_, err = fmt.Fprintf(w, "digraph G {\n node [shape=box];\n")
	if err != nil {
		return err
	}
	r.Funcs(func(f *monkit.Func) {
		if err != nil {
			return
		}
		success := f.Success()
		panics := f.Panics()

		var err_out bytes.Buffer
		total_errors := int64(0)
		for errname, count := range f.Errors() {
			_, err = fmt.Fprint(&err_out, escapeDotLabel("error %s: %d\n", errname,
				count))
			if err != nil {
				return
			}
			total_errors += count
		}

		_, err = fmt.Fprintf(w, " f%d [label=\"%s", f.Id(),
			escapeDotLabel("%s\ncurrent: %d, highwater: %d, success: %d, "+
				"errors: %d, panics: %d\n", f.FullName(), f.Current(), f.Highwater(),
				success, total_errors, panics))
		if err != nil {
			return
		}

		_, err = err_out.WriteTo(w)
		if err != nil {
			return
		}

		if success > 0 {
			_, err = fmt.Fprint(w, escapeDotLabel(
				"success times:\n%s", formatDist(f.SuccessTimes(), "        ")))
			if err != nil {
				return
			}
		}

		if total_errors+panics > 0 {
			_, err = fmt.Fprint(w, escapeDotLabel(
				"failure times:\n%s", formatDist(f.FailureTimes(), "        ")))
			if err != nil {
				return
			}
		}

		_, err = fmt.Fprint(w, "\"];\n")
		if err != nil {
			return
		}

		f.Parents(func(parent *monkit.Func) {
			if err != nil {
				return
			}
			if parent != nil {
				_, err = fmt.Fprintf(w, " f%d -> f%d;\n", parent.Id(), f.Id())
				if err != nil {
					return
				}
			} else {
				_, err = fmt.Fprintf(w, " r%d [label=\"entry\"];\n r%d -> f%d;\n",
					f.Id(), f.Id(), f.Id())
				if err != nil {
					return
				}
			}
		})
	})
	if err != nil {
		return err
	}
	_, err = fmt.Fprintf(w, "}\n")
	return err
}

// FuncsText finds all of the Funcs known by Registry r and writes information
// about them in a plain text format to w.
func FuncsText(r *monkit.Registry, w io.Writer) (err error) {
	r.Funcs(func(f *monkit.Func) {
		if err != nil {
			return
		}
		_, err = fmt.Fprintf(w, "[%d] %s\n  parents: ", f.Id(), f.FullName())
		if err != nil {
			return
		}
		printed := false
		f.Parents(func(parent *monkit.Func) {
			if err != nil {
				return
			}
			if printed {
				_, err = fmt.Fprint(w, ", ")
				if err != nil {
					return
				}
			} else {
				printed = true
			}
			if parent != nil {
				_, err = fmt.Fprintf(w, "%d", parent.Id())
				if err != nil {
					return
				}
			} else {
				_, err = fmt.Fprintf(w, "entry")
				if err != nil {
					return
				}
			}
		})
		var err_out bytes.Buffer
		total_errors := int64(0)
		for errname, count := range f.Errors() {
			_, err = fmt.Fprintf(&err_out, "  error %s: %d\n", errname, count)
			if err != nil {
				return
			}
			total_errors += count
		}
		_, err = fmt.Fprintf(w,
			"\n  current: %d, highwater: %d, success: %d, errors: %d, panics: %d\n",
			f.Current(), f.Highwater(), f.Success(), total_errors, f.Panics())
		if err != nil {
			return
		}
		_, err = err_out.WriteTo(w)
		if err != nil {
			return
		}
		_, err = fmt.Fprintf(w, "  success times:\n%s  failure times:\n%s\n",
			formatDist(f.SuccessTimes(), "    "),
			formatDist(f.FailureTimes(), "    "))
	})
	return err
}

// FuncsJSON finds all of the Funcs known by Registry r and writes information
// about them in the JSON format to w.
func FuncsJSON(r *monkit.Registry, w io.Writer) (err error) {
	lw := newListWriter(w)
	r.Funcs(func(f *monkit.Func) {
		lw.elem(formatFunc(f))
	})
	return lw.done()
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/base64"
	"errors"
	"flag"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"time"
)

var defaultPort int

var ptInfo ptServerInfo

// When a connection handler starts, +1 is written to this channel; when it
// ends, -1 is written.
var handlerChan = make(chan int)

func logDebug(format string, v ...interface{}) {
	fmt.Fprintf(os.Stderr, format+"\n", v...)
}

type websocketConn struct {
	Ws         *websocket
	Base64     bool
	messageBuf []byte
}

func (conn *websocketConn) Read(b []byte) (n int, err error) {
	for len(conn.messageBuf) == 0 {
		var m websocketMessage
		m, err = conn.Ws.ReadMessage()
		if err != nil {
			return
		}
		if conn.Base64 {
			if m.Opcode != 1 {
				err = errors.New(fmt.Sprintf("got non-text opcode %d with the base64 subprotocol", m.Opcode))
				return
			}
			conn.messageBuf = make([]byte, base64.StdEncoding.DecodedLen(len(m.Payload)))
			var num int
			num, err = base64.StdEncoding.Decode(conn.messageBuf, m.Payload)
			if err != nil {
				return
			}
			conn.messageBuf = conn.messageBuf[:num]
		} else {
			if m.Opcode != 2 {
				err = errors.New(fmt.Sprintf("got non-binary opcode %d with no subprotocol", m.Opcode))
				return
			}
			conn.messageBuf = m.Payload
		}
	}

	n = copy(b, conn.messageBuf)
	conn.messageBuf = conn.messageBuf[n:]

	return
}

func (conn *websocketConn) Write(b []byte) (n int, err error) {
	if conn.Base64 {
		buf := make([]byte, base64.StdEncoding.EncodedLen(len(b)))
		base64.StdEncoding.Encode(buf, b)
		err = conn.Ws.WriteMessage(1, buf)
		if err != nil {
			return
		}
		n = len(b)
	} else {
		err = conn.Ws.WriteMessage(2, b)
		n = len(b)
	}
	return
}

func (conn *websocketConn) Close() (err error) {
	err = conn.Ws.WriteFrame(8, nil)
	if err != nil {
		conn.Ws.Conn.Close()
		return
	}
	err = conn.Ws.Conn.Close()
	return
}

func (conn *websocketConn) LocalAddr() net.Addr {
	return conn.Ws.Conn.LocalAddr()
}

func (conn *websocketConn) RemoteAddr() net.Addr {
	return conn.Ws.Conn.RemoteAddr()
}

func (conn *websocketConn) SetDeadline(t time.Time) error {
	return conn.Ws.Conn.SetDeadline(t)
}

func (conn *websocketConn) SetReadDeadline(t time.Time) error {
	return conn.Ws.Conn.SetReadDeadline(t)
}

func (conn *websocketConn) SetWriteDeadline(t time.Time) error {
	return conn.Ws.Conn.SetWriteDeadline(t)
}

func NewWebsocketConn(ws *websocket) websocketConn {
	var conn websocketConn
	conn.Ws = ws
	conn.Base64 = (ws.Subprotocol == "base64")
	return conn
}

func proxy(local *net.TCPConn, conn *websocketConn) {
	var wg sync.WaitGroup

	wg.Add(2)

	go func() {
		_, err := io.Copy(conn, local)
		if err != nil {
			logDebug("error copying ORPort to WebSocket: " + err.Error())
		}
		local.CloseRead()
		conn.Close()
		wg.Done()
	}()

	go func() {
		_, err := io.Copy(local, conn)
		if err != nil {
			logDebug("error copying WebSocket to ORPort: " + err.Error())
		}
		local.CloseWrite()
		conn.Close()
		wg.Done()
	}()

	wg.Wait()
}

func websocketHandler(ws *websocket) {
	conn := NewWebsocketConn(ws)

	handlerChan <- 1
	defer func() {
		handlerChan <- -1
	}()

	s, err := net.DialTCP("tcp", nil, ptInfo.OrAddr)
	if err != nil {
		logDebug("Failed to connect to ORPort: " + err.Error())
		return
	}

	proxy(s, &conn)
}

func startListener(addr *net.TCPAddr) (*net.TCPListener, error) {
	ln, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return nil, err
	}
	go func() {
		var config websocketConfig
		config.Subprotocols = []string{"base64"}
		config.MaxMessageSize = 1500
		http.Handle("/", config.Handler(websocketHandler))
		err = http.Serve(ln, nil)
		if err != nil {
			logDebug("http.Serve: " + err.Error())
		}
	}()
	return ln, nil
}

func main() {
	const ptMethodName = "websocket"

	flag.IntVar(&defaultPort, "port", 0, "port to listen on if unspecified by Tor")
	flag.Parse()

	ptInfo = ptServerSetup([]string{ptMethodName})

	listeners := make([]*net.TCPListener, 0)
	for _, bindAddr := range ptInfo.BindAddrs {
		// When tor tells us a port of 0, we are supposed to pick a
		// random port. But we actually want to use the configured port.
		if bindAddr.Addr.Port == 0 {
			bindAddr.Addr.Port = defaultPort
		}

		ln, err := startListener(bindAddr.Addr)
		if err != nil {
			ptSmethodError(bindAddr.MethodName, err.Error())
		}
		ptSmethod(bindAddr.MethodName, ln.Addr())
		listeners = append(listeners, ln)
	}
	ptSmethodsDone()

	var numHandlers int = 0

	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, os.Interrupt)
	var sigint bool = false
	for !sigint {
		select {
		case n := <-handlerChan:
			numHandlers += n
		case <-signalChan:
			logDebug("SIGINT")
			sigint = true
		}
	}

	for _, ln := range listeners {
		ln.Close()
	}

	sigint = false
	for numHandlers != 0 && !sigint {
		select {
		case n := <-handlerChan:
			numHandlers += n
		case <-signalChan:
			logDebug("SIGINT")
			sigint = true
		}
	}
}
<commit_msg>Increase MaxMessageSize to account for base64 overhead.<commit_after>package main

import (
	"encoding/base64"
	"errors"
	"flag"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"time"
)

var defaultPort int

var ptInfo ptServerInfo

// When a connection handler starts, +1 is written to this channel; when it
// ends, -1 is written.
var handlerChan = make(chan int)

func logDebug(format string, v ...interface{}) {
	fmt.Fprintf(os.Stderr, format+"\n", v...)
}

type websocketConn struct {
	Ws         *websocket
	Base64     bool
	messageBuf []byte
}

func (conn *websocketConn) Read(b []byte) (n int, err error) {
	for len(conn.messageBuf) == 0 {
		var m websocketMessage
		m, err = conn.Ws.ReadMessage()
		if err != nil {
			return
		}
		if conn.Base64 {
			if m.Opcode != 1 {
				err = errors.New(fmt.Sprintf("got non-text opcode %d with the base64 subprotocol", m.Opcode))
				return
			}
			conn.messageBuf = make([]byte, base64.StdEncoding.DecodedLen(len(m.Payload)))
			var num int
			num, err = base64.StdEncoding.Decode(conn.messageBuf, m.Payload)
			if err != nil {
				return
			}
			conn.messageBuf = conn.messageBuf[:num]
		} else {
			if m.Opcode != 2 {
				err = errors.New(fmt.Sprintf("got non-binary opcode %d with no subprotocol", m.Opcode))
				return
			}
			conn.messageBuf = m.Payload
		}
	}

	n = copy(b, conn.messageBuf)
	conn.messageBuf = conn.messageBuf[n:]

	return
}

func (conn *websocketConn) Write(b []byte) (n int, err error) {
	if conn.Base64 {
		buf := make([]byte, base64.StdEncoding.EncodedLen(len(b)))
		base64.StdEncoding.Encode(buf, b)
		err = conn.Ws.WriteMessage(1, buf)
		if err != nil {
			return
		}
		n = len(b)
	} else {
		err = conn.Ws.WriteMessage(2, b)
		n = len(b)
	}
	return
}

func (conn *websocketConn) Close() (err error) {
	err = conn.Ws.WriteFrame(8, nil)
	if err != nil {
		conn.Ws.Conn.Close()
		return
	}
	err = conn.Ws.Conn.Close()
	return
}

func (conn *websocketConn) LocalAddr() net.Addr {
	return conn.Ws.Conn.LocalAddr()
}

func (conn *websocketConn) RemoteAddr() net.Addr {
	return conn.Ws.Conn.RemoteAddr()
}

func (conn *websocketConn) SetDeadline(t time.Time) error {
	return conn.Ws.Conn.SetDeadline(t)
}

func (conn *websocketConn) SetReadDeadline(t time.Time) error {
	return conn.Ws.Conn.SetReadDeadline(t)
}

func (conn *websocketConn) SetWriteDeadline(t time.Time) error {
	return conn.Ws.Conn.SetWriteDeadline(t)
}

func NewWebsocketConn(ws *websocket) websocketConn {
	var conn websocketConn
	conn.Ws = ws
	conn.Base64 = (ws.Subprotocol == "base64")
	return conn
}

func proxy(local *net.TCPConn, conn *websocketConn) {
	var wg sync.WaitGroup

	wg.Add(2)

	go func() {
		_, err := io.Copy(conn, local)
		if err != nil {
			logDebug("error copying ORPort to WebSocket: " + err.Error())
		}
		local.CloseRead()
		conn.Close()
		wg.Done()
	}()

	go func() {
		_, err := io.Copy(local, conn)
		if err != nil {
			logDebug("error copying WebSocket to ORPort: " + err.Error())
		}
		local.CloseWrite()
		conn.Close()
		wg.Done()
	}()

	wg.Wait()
}

func websocketHandler(ws *websocket) {
	conn := NewWebsocketConn(ws)

	handlerChan <- 1
	defer func() {
		handlerChan <- -1
	}()

	s, err := net.DialTCP("tcp", nil, ptInfo.OrAddr)
	if err != nil {
		logDebug("Failed to connect to ORPort: " + err.Error())
		return
	}

	proxy(s, &conn)
}

func startListener(addr *net.TCPAddr) (*net.TCPListener, error) {
	ln, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return nil, err
	}
	go func() {
		var config websocketConfig
		config.Subprotocols = []string{"base64"}
		config.MaxMessageSize = 2500
		http.Handle("/", config.Handler(websocketHandler))
		err = http.Serve(ln, nil)
		if err != nil {
			logDebug("http.Serve: " + err.Error())
		}
	}()
	return ln, nil
}

func main() {
	const ptMethodName = "websocket"

	flag.IntVar(&defaultPort, "port", 0, "port to listen on if unspecified by Tor")
	flag.Parse()

	ptInfo = ptServerSetup([]string{ptMethodName})

	listeners := make([]*net.TCPListener, 0)
	for _, bindAddr := range ptInfo.BindAddrs {
		// When tor tells us a port of 0, we are supposed to pick a
		// random port. But we actually want to use the configured port.
		if bindAddr.Addr.Port == 0 {
			bindAddr.Addr.Port = defaultPort
		}

		ln, err := startListener(bindAddr.Addr)
		if err != nil {
			ptSmethodError(bindAddr.MethodName, err.Error())
		}
		ptSmethod(bindAddr.MethodName, ln.Addr())
		listeners = append(listeners, ln)
	}
	ptSmethodsDone()

	var numHandlers int = 0

	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, os.Interrupt)
	var sigint bool = false
	for !sigint {
		select {
		case n := <-handlerChan:
			numHandlers += n
		case <-signalChan:
			logDebug("SIGINT")
			sigint = true
		}
	}

	for _, ln := range listeners {
		ln.Close()
	}

	sigint = false
	for numHandlers != 0 && !sigint {
		select {
		case n := <-handlerChan:
			numHandlers += n
		case <-signalChan:
			logDebug("SIGINT")
			sigint = true
		}
	}
}
<|endoftext|>


<commit_before>package acceptance

import (
	"net/http"
	"strings"
	"time"

	"github.com/cloudfoundry-incubator/notifications/acceptance/support"
	"github.com/cloudfoundry-incubator/notifications/application"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Send a notification to an email", func() {
	It("sends a single notification to an email", func() {
		var templateID string
		var response support.NotifyResponse
		clientID := "notifications-sender"
		clientToken := GetClientTokenFor(clientID, "uaa")
		client := support.NewClient(Servers.Notifications.URL())

		By("registering a notifications", func() {
			code, err := client.Notifications.Register(clientToken.Access, support.RegisterClient{
				SourceName: "Notifications Sender",
				Notifications: map[string]support.RegisterNotification{
					"acceptance-test": {
						Description: "Acceptance Test",
					},
				},
			})
			Expect(err).NotTo(HaveOccurred())
			Expect(code).To(Equal(http.StatusNoContent))
		})

		By("creating a new template", func() {
			var status int
			var err error
			status, templateID, err = client.Templates.Create(clientToken.Access, support.Template{
				Name:    "Star Trek",
				Subject: "Boldness {{.Subject}}",
				HTML:    "
Enterprise
{{.HTML}}
{{.Endorsement}}
",
				Text:    "Enterprise\n{{.Text}}\n{{.Endorsement}}",
			})
			Expect(err).NotTo(HaveOccurred())
			Expect(status).To(Equal(http.StatusCreated))
			Expect(templateID).NotTo(Equal(""))
		})

		By("assigning the template to a client", func() {
			status, err := client.Templates.AssignToClient(clientToken.Access, clientID, templateID)
			Expect(err).NotTo(HaveOccurred())
			Expect(status).To(Equal(http.StatusNoContent))
		})

		By("sending a notification to an email address", func() {
			status, responses, err := client.Notify.Email(clientToken.Access, "John User <user@example.com>", support.Notify{
				HTML:    "this is an acceptance test",
				Text:    "some text for the email",
				Subject: "my-special-subject",
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(status).To(Equal(http.StatusOK))

			Expect(responses).To(HaveLen(1))
			response = responses[0]
			Expect(response.Status).To(Equal("queued"))
			Expect(response.Recipient).To(Equal("user@example.com"))
			Expect(response.VCAPRequestID).To(Equal("some-totally-fake-vcap-request-id"))
			Expect(GUIDRegex.MatchString(response.NotificationID)).To(BeTrue())
		})

		By("verifying the message was sent", func() {
			Eventually(func() int {
				return len(Servers.SMTP.Deliveries)
			}, 1*time.Second).Should(Equal(1))
			delivery := Servers.SMTP.Deliveries[0]

			env := application.NewEnvironment()
			Expect(delivery.Sender).To(Equal(env.Sender))
			Expect(delivery.Recipients).To(Equal([]string{"user@example.com"}))

			data := strings.Split(string(delivery.Data), "\n")
			Expect(data).To(ContainElement("X-CF-Client-ID: notifications-sender"))
			Expect(data).To(ContainElement("X-CF-Notification-ID: " + response.NotificationID))
			Expect(data).To(ContainElement(ContainSubstring("X-CF-Notification-Timestamp: ")))

			var timestampString string
			prefix := "X-CF-Notification-Timestamp: "
			for _, line := range data {
				if !strings.Contains(line, prefix) {
					continue
				}
				timestampString = strings.TrimPrefix(line, prefix)
			}
			Expect(timestampString).NotTo(BeEmpty())

			timestampDate, err := time.Parse(time.RFC3339, timestampString)
			Expect(err).NotTo(HaveOccurred())
			Expect(timestampDate).To(BeTemporally("~", time.Now(), 15*time.Second))

			Expect(data).To(ContainElement("Subject: Boldness my-special-subject"))
			Expect(data).To(ContainElement("\t\t
Enterprise
this is an acceptance test
This messa="))
			Expect(data).To(ContainElement("ge was sent directly to your email address.
"))
			Expect(data).To(ContainElement("Enterprise"))
			Expect(data).To(ContainElement("some text for the email"))
			Expect(data).To(ContainElement("This message was sent directly to your email address."))
		})

		By("confirming that the client notifications list remains unaffected", func() {
			status, list, err := client.Notifications.List(GetClientTokenFor("notifications-sender", "uaa").Access)
			Expect(err).NotTo(HaveOccurred())
			Expect(status).To(Equal(http.StatusOK))
			Expect(list).To(HaveLen(1))

			notificationsSender := list[clientID]
			Expect(notificationsSender.Name).To(Equal("Notifications Sender"))
			Expect(notificationsSender.Template).To(Equal(templateID))
			Expect(notificationsSender.Notifications).To(HaveLen(1))

			acceptanceNotification := notificationsSender.Notifications["acceptance-test"]
			Expect(acceptanceNotification.Description).To(Equal("Acceptance Test"))
			Expect(acceptanceNotification.Template).To(Equal("default"))
			Expect(acceptanceNotification.Critical).To(BeFalse())
		})
	})
})
<commit_msg>Test sending notification to email works with Identity Zones [#97314780]<commit_after>package acceptance

import (
	"net/http"
	"strings"
	"time"

	"github.com/cloudfoundry-incubator/notifications/acceptance/support"
	"github.com/cloudfoundry-incubator/notifications/application"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Send a notification to an email", func() {
	It("sends a single notification to an email", func() {
		var templateID string
		var response support.NotifyResponse
		clientID := "notifications-sender"
		clientToken := GetClientTokenFor(clientID, "testzone1")
		client := support.NewClient(Servers.Notifications.URL())

		By("registering a notifications", func() {
			code, err := client.Notifications.Register(clientToken.Access, support.RegisterClient{
				SourceName: "Notifications Sender",
				Notifications: map[string]support.RegisterNotification{
					"acceptance-test": {
						Description: "Acceptance Test",
					},
				},
			})
			Expect(err).NotTo(HaveOccurred())
			Expect(code).To(Equal(http.StatusNoContent))
		})

		By("creating a new template", func() {
			var status int
			var err error
			status, templateID, err = client.Templates.Create(clientToken.Access, support.Template{
				Name:    "Star Trek",
				Subject: "Boldness {{.Subject}}",
				HTML:    "
Enterprise
{{.HTML}}
{{.Endorsement}}
",
				Text:    "Enterprise\n{{.Text}}\n{{.Endorsement}}",
			})
			Expect(err).NotTo(HaveOccurred())
			Expect(status).To(Equal(http.StatusCreated))
			Expect(templateID).NotTo(Equal(""))
		})

		By("assigning the template to a client", func() {
			status, err := client.Templates.AssignToClient(clientToken.Access, clientID, templateID)
			Expect(err).NotTo(HaveOccurred())
			Expect(status).To(Equal(http.StatusNoContent))
		})

		By("sending a notification to an email address", func() {
			status, responses, err := client.Notify.Email(clientToken.Access, "John User <user@example.com>", support.Notify{
				HTML:    "this is an acceptance test",
				Text:    "some text for the email",
				Subject: "my-special-subject",
			})

			Expect(err).NotTo(HaveOccurred())
			Expect(status).To(Equal(http.StatusOK))

			Expect(responses).To(HaveLen(1))
			response = responses[0]
			Expect(response.Status).To(Equal("queued"))
			Expect(response.Recipient).To(Equal("user@example.com"))
			Expect(response.VCAPRequestID).To(Equal("some-totally-fake-vcap-request-id"))
			Expect(GUIDRegex.MatchString(response.NotificationID)).To(BeTrue())
		})

		By("verifying the message was sent", func() {
			Eventually(func() int {
				return len(Servers.SMTP.Deliveries)
			}, 1*time.Second).Should(Equal(1))
			delivery := Servers.SMTP.Deliveries[0]

			env := application.NewEnvironment()
			Expect(delivery.Sender).To(Equal(env.Sender))
			Expect(delivery.Recipients).To(Equal([]string{"user@example.com"}))

			data := strings.Split(string(delivery.Data), "\n")
			Expect(data).To(ContainElement("X-CF-Client-ID: notifications-sender"))
			Expect(data).To(ContainElement("X-CF-Notification-ID: " + response.NotificationID))
			Expect(data).To(ContainElement(ContainSubstring("X-CF-Notification-Timestamp: ")))

			var timestampString string
			prefix := "X-CF-Notification-Timestamp: "
			for _, line := range data {
				if !strings.Contains(line, prefix) {
					continue
				}
				timestampString = strings.TrimPrefix(line, prefix)
			}
			Expect(timestampString).NotTo(BeEmpty())

			timestampDate, err := time.Parse(time.RFC3339, timestampString)
			Expect(err).NotTo(HaveOccurred())
			Expect(timestampDate).To(BeTemporally("~", time.Now(), 15*time.Second))

			Expect(data).To(ContainElement("Subject: Boldness my-special-subject"))
			Expect(data).To(ContainElement("\t\t
Enterprise
this is an acceptance test
This messa="))
			Expect(data).To(ContainElement("ge was sent directly to your email address.
"))
			Expect(data).To(ContainElement("Enterprise"))
			Expect(data).To(ContainElement("some text for the email"))
			Expect(data).To(ContainElement("This message was sent directly to your email address."))
		})

		By("confirming that the client notifications list remains unaffected", func() {
			status, list, err := client.Notifications.List(GetClientTokenFor("notifications-sender", "uaa").Access)
			Expect(err).NotTo(HaveOccurred())
			Expect(status).To(Equal(http.StatusOK))
			Expect(list).To(HaveLen(1))

			notificationsSender := list[clientID]
			Expect(notificationsSender.Name).To(Equal("Notifications Sender"))
			Expect(notificationsSender.Template).To(Equal(templateID))
			Expect(notificationsSender.Notifications).To(HaveLen(1))

			acceptanceNotification := notificationsSender.Notifications["acceptance-test"]
			Expect(acceptanceNotification.Description).To(Equal("Acceptance Test"))
			Expect(acceptanceNotification.Template).To(Equal("default"))
			Expect(acceptanceNotification.Critical).To(BeFalse())
		})
	})
})
<|endoftext|>


<commit_before>/*
Copyright 2015 Google Inc. All rights reserved.

Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file or at
https://developers.google.com/open-source/licenses/bsd
*/

package privet

import (
	"os"
	"sync"

	"github.com/google/cups-connector/lib"
)

// Privet managers local discovery and printing.
type Privet struct {
	xsrf      xsrfSecret
	apis      map[string]*privetAPI
	apisMutex sync.RWMutex // Protects apis
	zc        *zeroconf

	jobs chan *lib.Job
	jc   jobCache

	gcpBaseURL        string
	getProximityToken func(string, string) ([]byte, int, error)
	createTempFile    func() (*os.File, error)
}

// NewPrivet constructs a new Privet object.
//
// getProximityToken should be GoogleCloudPrint.ProximityToken()
// createTempFile should be cups.CreateTempFile()
func NewPrivet(gcpBaseURL string, getProximityToken func(string, string) ([]byte, int, error), createTempFile func() (*os.File, error)) (*Privet, error) {
	zc, err := newZeroconf()
	if err != nil {
		return nil, err
	}

	p := Privet{
		xsrf: newXSRFSecret(),
		apis: make(map[string]*privetAPI),
		zc:   zc,

		jobs: make(chan *lib.Job, 10),
		jc:   *newJobCache(),

		gcpBaseURL:        gcpBaseURL,
		getProximityToken: getProximityToken,
		createTempFile:    createTempFile,
	}

	return &p, nil
}

// AddPrinter makes a printer available locally.
func (p *Privet) AddPrinter(printer lib.Printer, getPrinter func() (lib.Printer, bool)) error {
	getProximityToken := func(user string) ([]byte, int, error) { return p.getProximityToken(printer.GCPID, user) }
	api, err := newPrivetAPI(printer.GCPID, p.gcpBaseURL, p.xsrf, &p.jc, p.jobs, getPrinter, getProximityToken, p.createTempFile)
	if err != nil {
		return err
	}

	online := false
	if printer.GCPID != "" {
		online = true
	}

	# TODO once we add local-only support we should hide the append behind an if ! local-only
	var localDefaultDisplayName = printer.DefaultDisplayName
	localDefaultDisplayName = fmt.Sprintf("%s (local)", localDefaultDisplayName)
	err = p.zc.addPrinter(printer.GCPID, printer.Name, api.port(), localDefaultDisplayName, p.gcpBaseURL, printer.GCPID, online)
	if err != nil {
		api.quit()
		return err
	}

	p.apisMutex.Lock()
	defer p.apisMutex.Unlock()

	p.apis[printer.GCPID] = api

	return nil
}

// UpdatePrinter updates a printer's TXT mDNS record.
func (p *Privet) UpdatePrinter(diff *lib.PrinterDiff) error {
	// API never needs to be updated

	online := false
	if diff.Printer.GCPID != "" {
		online = true
	}
	return p.zc.updatePrinterTXT(diff.Printer.GCPID, diff.Printer.DefaultDisplayName, p.gcpBaseURL, diff.Printer.GCPID, online)
}

// DeletePrinter removes a printer from Privet.
func (p *Privet) DeletePrinter(gcpID string) error {
	p.apisMutex.Lock()
	defer p.apisMutex.Unlock()

	err := p.zc.removePrinter(gcpID)
	if api, ok := p.apis[gcpID]; ok {
		api.quit()
		delete(p.apis, gcpID)
	}
	return err
}

// Jobs returns a channel that emits new print jobs.
func (p *Privet) Jobs() <-chan *lib.Job {
	return p.jobs
}

func (p *Privet) Quit() {
	p.apisMutex.Lock()
	defer p.apisMutex.Unlock()

	p.zc.quit()
	for gcpID, api := range p.apis {
		api.quit()
		delete(p.apis, gcpID)
	}
}
<commit_msg>also append (local) on updatePrinter()<commit_after>/*
Copyright 2015 Google Inc. All rights reserved.

Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file or at
https://developers.google.com/open-source/licenses/bsd
*/

package privet

import (
	"os"
	"sync"

	"github.com/google/cups-connector/lib"
)

// Privet managers local discovery and printing.
type Privet struct {
	xsrf      xsrfSecret
	apis      map[string]*privetAPI
	apisMutex sync.RWMutex // Protects apis
	zc        *zeroconf

	jobs chan *lib.Job
	jc   jobCache

	gcpBaseURL        string
	getProximityToken func(string, string) ([]byte, int, error)
	createTempFile    func() (*os.File, error)
}

// NewPrivet constructs a new Privet object.
//
// getProximityToken should be GoogleCloudPrint.ProximityToken()
// createTempFile should be cups.CreateTempFile()
func NewPrivet(gcpBaseURL string, getProximityToken func(string, string) ([]byte, int, error), createTempFile func() (*os.File, error)) (*Privet, error) {
	zc, err := newZeroconf()
	if err != nil {
		return nil, err
	}

	p := Privet{
		xsrf: newXSRFSecret(),
		apis: make(map[string]*privetAPI),
		zc:   zc,

		jobs: make(chan *lib.Job, 10),
		jc:   *newJobCache(),

		gcpBaseURL:        gcpBaseURL,
		getProximityToken: getProximityToken,
		createTempFile:    createTempFile,
	}

	return &p, nil
}

// AddPrinter makes a printer available locally.
func (p *Privet) AddPrinter(printer lib.Printer, getPrinter func() (lib.Printer, bool)) error {
	getProximityToken := func(user string) ([]byte, int, error) { return p.getProximityToken(printer.GCPID, user) }
	api, err := newPrivetAPI(printer.GCPID, p.gcpBaseURL, p.xsrf, &p.jc, p.jobs, getPrinter, getProximityToken, p.createTempFile)
	if err != nil {
		return err
	}

	online := false
	if printer.GCPID != "" {
		online = true
	}

	# TODO once we add local-only support we should hide the append behind an if ! local-only
	var localDefaultDisplayName = printer.DefaultDisplayName
	localDefaultDisplayName = fmt.Sprintf("%s (local)", localDefaultDisplayName)
	err = p.zc.addPrinter(printer.GCPID, printer.Name, api.port(), localDefaultDisplayName, p.gcpBaseURL, printer.GCPID, online)
	if err != nil {
		api.quit()
		return err
	}

	p.apisMutex.Lock()
	defer p.apisMutex.Unlock()

	p.apis[printer.GCPID] = api

	return nil
}

// UpdatePrinter updates a printer's TXT mDNS record.
func (p *Privet) UpdatePrinter(diff *lib.PrinterDiff) error {
	// API never needs to be updated

	online := false
	if diff.Printer.GCPID != "" {
		online = true
	}

        // TODO once we add local-only support we should hide the append behind an if ! local-only
        var localDefaultDisplayName = diff.Printer.DefaultDisplayName
        localDefaultDisplayName = fmt.Sprintf("%s (local)", localDefaultDisplayName)

        return p.zc.updatePrinterTXT(diff.Printer.GCPID, localDefaultDisplayName, p.gcpBaseURL, diff.Printer.GCPID, online)
}

// DeletePrinter removes a printer from Privet.
func (p *Privet) DeletePrinter(gcpID string) error {
	p.apisMutex.Lock()
	defer p.apisMutex.Unlock()

	err := p.zc.removePrinter(gcpID)
	if api, ok := p.apis[gcpID]; ok {
		api.quit()
		delete(p.apis, gcpID)
	}
	return err
}

// Jobs returns a channel that emits new print jobs.
func (p *Privet) Jobs() <-chan *lib.Job {
	return p.jobs
}

func (p *Privet) Quit() {
	p.apisMutex.Lock()
	defer p.apisMutex.Unlock()

	p.zc.quit()
	for gcpID, api := range p.apis {
		api.quit()
		delete(p.apis, gcpID)
	}
}
<|endoftext|>