<commit_before>package libpod

import (
	"os"
	"sync"

	is "github.com/containers/image/storage"
	"github.com/containers/image/types"
	"github.com/containers/storage"
	"github.com/pkg/errors"
	"github.com/ulule/deepcopier"
)

// A RuntimeOption is a functional option which alters the Runtime created by
// NewRuntime
type RuntimeOption func(*Runtime) error

// Runtime is the core libpod runtime
type Runtime struct {
	config         *RuntimeConfig
	state          State
	store          storage.Store
	storageService *storageService
	imageContext   *types.SystemContext
	ociRuntime     *OCIRuntime
	valid          bool
	lock           sync.RWMutex
}

// RuntimeConfig contains configuration options used to set up the runtime
type RuntimeConfig struct {
	StorageConfig         storage.StoreOptions
	ImageDefaultTransport string
	InsecureRegistries    []string
	Registries            []string
	SignaturePolicyPath   string
	RuntimePath           string
	ConmonPath            string
	ConmonEnvVars         []string
	CgroupManager         string
	StaticDir             string
	TmpDir                string
	SelinuxEnabled        bool
	PidsLimit             int64
	MaxLogSize            int64
	NoPivotRoot           bool
}

var (
	defaultRuntimeConfig = RuntimeConfig{
		// Leave this empty so containers/storage will use its defaults
		StorageConfig:         storage.StoreOptions{},
		ImageDefaultTransport: "docker://",
		RuntimePath:           "/usr/bin/runc",
		ConmonPath:            "/usr/local/libexec/crio/conmon",
		ConmonEnvVars: []string{
			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
		},
		CgroupManager:  "cgroupfs",
		StaticDir:      "/var/lib/libpod",
		TmpDir:         "/var/run/libpod",
		SelinuxEnabled: false,
		PidsLimit:      1024,
		MaxLogSize:     -1,
		NoPivotRoot:    false,
	}
)

// NewRuntime creates a new container runtime
// Options can be passed to override the default configuration for the runtime
func NewRuntime(options ...RuntimeOption) (*Runtime, error) {
	runtime := new(Runtime)
	runtime.config = new(RuntimeConfig)

	// Copy the default configuration
	deepcopier.Copy(defaultRuntimeConfig).To(runtime.config)

	// Overwrite it with user-given configuration options
	for _, opt := range options {
		if err := opt(runtime); err != nil {
			return nil, errors.Wrapf(err, "error configuring runtime")
		}
	}

	// Set up containers/storage
	store, err := storage.GetStore(runtime.config.StorageConfig)
	if err != nil {
		return nil, err
	}
	runtime.store = store
	is.Transport.SetStore(store)

	// TODO remove StorageImageServer and make its functions work directly
	// on Runtime (or convert to something that satisfies an image)
	storageService, err := getStorageService(runtime.store)
	if err != nil {
		return nil, err
	}
	runtime.storageService = storageService

	// Set up containers/image
	runtime.imageContext = &types.SystemContext{
		SignaturePolicyPath: runtime.config.SignaturePolicyPath,
	}

	// Set up the state
	state, err := NewInMemoryState()
	if err != nil {
		return nil, err
	}
	runtime.state = state

	// Make an OCI runtime to perform container operations
	ociRuntime, err := newOCIRuntime("runc", runtime.config.RuntimePath,
		runtime.config.ConmonPath, runtime.config.ConmonEnvVars,
		runtime.config.CgroupManager, runtime.config.TmpDir,
		runtime.config.MaxLogSize, runtime.config.NoPivotRoot)
	if err != nil {
		return nil, err
	}
	runtime.ociRuntime = ociRuntime

	// Make the static files directory if it does not exist
	if err := os.MkdirAll(runtime.config.StaticDir, 0755); err != nil {
		// The directory is allowed to exist
		if !os.IsExist(err) {
			return nil, errors.Wrapf(err, "error creating runtime static files directory %s",
				runtime.config.StaticDir)
		}
	}

	// Make the per-boot files directory if it does not exist
	if err := os.MkdirAll(runtime.config.TmpDir, 0755); err != nil {
		// The directory is allowed to exist
		if !os.IsExist(err) {
			return nil, errors.Wrapf(err, "error creating runtime temporary files directory %s",
				runtime.config.TmpDir)
		}
	}

	// Mark the runtime as valid - ready to be used, cannot be modified
	// further
	runtime.valid = true

	return runtime, nil
}

// GetConfig returns a copy of the configuration used by the runtime
func (r *Runtime) GetConfig() *RuntimeConfig {
	r.lock.RLock()
	defer r.lock.RUnlock()

	if !r.valid {
		return nil
	}

	config := new(RuntimeConfig)

	// Copy so the caller won't be able to modify the actual config
	deepcopier.Copy(r.config).To(config)

	return config
}

// Shutdown shuts down the runtime and associated containers and storage
// If force is true, containers and mounted storage will be shut down before
// cleaning up; if force is false, an error will be returned if there are
// still containers running or mounted
func (r *Runtime) Shutdown(force bool) error {
	r.lock.Lock()
	defer r.lock.Unlock()

	if !r.valid {
		return ErrRuntimeStopped
	}

	r.valid = false

	_, err := r.store.Shutdown(force)
	return err
}
<commit_msg>Shut down libpod runtime's store if error occurs in NewRuntime<commit_after>package libpod

import (
	"os"
	"sync"

	is "github.com/containers/image/storage"
	"github.com/containers/image/types"
	"github.com/containers/storage"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/ulule/deepcopier"
)

// A RuntimeOption is a functional option which alters the Runtime created by
// NewRuntime
type RuntimeOption func(*Runtime) error

// Runtime is the core libpod runtime
type Runtime struct {
	config         *RuntimeConfig
	state          State
	store          storage.Store
	storageService *storageService
	imageContext   *types.SystemContext
	ociRuntime     *OCIRuntime
	valid          bool
	lock           sync.RWMutex
}

// RuntimeConfig contains configuration options used to set up the runtime
type RuntimeConfig struct {
	StorageConfig         storage.StoreOptions
	ImageDefaultTransport string
	InsecureRegistries    []string
	Registries            []string
	SignaturePolicyPath   string
	RuntimePath           string
	ConmonPath            string
	ConmonEnvVars         []string
	CgroupManager         string
	StaticDir             string
	TmpDir                string
	SelinuxEnabled        bool
	PidsLimit             int64
	MaxLogSize            int64
	NoPivotRoot           bool
}

var (
	defaultRuntimeConfig = RuntimeConfig{
		// Leave this empty so containers/storage will use its defaults
		StorageConfig:         storage.StoreOptions{},
		ImageDefaultTransport: "docker://",
		RuntimePath:           "/usr/bin/runc",
		ConmonPath:            "/usr/local/libexec/crio/conmon",
		ConmonEnvVars: []string{
			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
		},
		CgroupManager:  "cgroupfs",
		StaticDir:      "/var/lib/libpod",
		TmpDir:         "/var/run/libpod",
		SelinuxEnabled: false,
		PidsLimit:      1024,
		MaxLogSize:     -1,
		NoPivotRoot:    false,
	}
)

// NewRuntime creates a new container runtime
// Options can be passed to override the default configuration for the runtime
func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
	runtime = new(Runtime)
	runtime.config = new(RuntimeConfig)

	// Copy the default configuration
	deepcopier.Copy(defaultRuntimeConfig).To(runtime.config)

	// Overwrite it with user-given configuration options
	for _, opt := range options {
		if err := opt(runtime); err != nil {
			return nil, errors.Wrapf(err, "error configuring runtime")
		}
	}

	// Set up containers/storage
	store, err := storage.GetStore(runtime.config.StorageConfig)
	if err != nil {
		return nil, err
	}
	runtime.store = store
	is.Transport.SetStore(store)
	defer func() {
		if err != nil {
			// Don't forcibly shut down
			// We could be opening a store in use by another libpod
			_, err2 := runtime.store.Shutdown(false)
			if err2 != nil {
				logrus.Errorf("Error removing store for partially-created runtime: %s", err2)
			}
		}
	}()

	// Set up a storage service for creating container root filesystems from
	// images
	storageService, err := getStorageService(runtime.store)
	if err != nil {
		return nil, err
	}
	runtime.storageService = storageService

	// Set up containers/image
	runtime.imageContext = &types.SystemContext{
		SignaturePolicyPath: runtime.config.SignaturePolicyPath,
	}

	// Set up the state
	state, err := NewInMemoryState()
	if err != nil {
		return nil, err
	}
	runtime.state = state

	// Make an OCI runtime to perform container operations
	ociRuntime, err := newOCIRuntime("runc", runtime.config.RuntimePath,
		runtime.config.ConmonPath, runtime.config.ConmonEnvVars,
		runtime.config.CgroupManager, runtime.config.TmpDir,
		runtime.config.MaxLogSize, runtime.config.NoPivotRoot)
	if err != nil {
		return nil, err
	}
	runtime.ociRuntime = ociRuntime

	// Make the static files directory if it does not exist
	if err := os.MkdirAll(runtime.config.StaticDir, 0755); err != nil {
		// The directory is allowed to exist
		if !os.IsExist(err) {
			return nil, errors.Wrapf(err, "error creating runtime static files directory %s",
				runtime.config.StaticDir)
		}
	}

	// Make the per-boot files directory if it does not exist
	if err := os.MkdirAll(runtime.config.TmpDir, 0755); err != nil {
		// The directory is allowed to exist
		if !os.IsExist(err) {
			return nil, errors.Wrapf(err, "error creating runtime temporary files directory %s",
				runtime.config.TmpDir)
		}
	}

	// Mark the runtime as valid - ready to be used, cannot be modified
	// further
	runtime.valid = true

	return runtime, nil
}

// GetConfig returns a copy of the configuration used by the runtime
func (r *Runtime) GetConfig() *RuntimeConfig {
	r.lock.RLock()
	defer r.lock.RUnlock()

	if !r.valid {
		return nil
	}

	config := new(RuntimeConfig)

	// Copy so the caller won't be able to modify the actual config
	deepcopier.Copy(r.config).To(config)

	return config
}

// Shutdown shuts down the runtime and associated containers and storage
// If force is true, containers and mounted storage will be shut down before
// cleaning up; if force is false, an error will be returned if there are
// still containers running or mounted
func (r *Runtime) Shutdown(force bool) error {
	r.lock.Lock()
	defer r.lock.Unlock()

	if !r.valid {
		return ErrRuntimeStopped
	}

	r.valid = false

	_, err := r.store.Shutdown(force)
	return err
}
<|endoftext|>


<commit_before>package wiki

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/cooper/quiki/wikifier"
)

// DisplayFile represents a plain text file to display.
type DisplayFile struct {

	// file name relative to wiki root.
	// path delimiter '/' is always used, regardless of OS.
	File string `json:"file,omitempty"`

	// absolute file path of the file.
	// OS-specific path delimiter is used.
	Path string `json:"path,omitempty"`

	// the plain text file content
	Content string `json:"-"`

	// time when the file was last modified
	Modified *time.Time `json:"modified,omitempty"`

	// for pages/models/etc, parser warnings and error
	Warnings []wikifier.Warning `json:"parse_warnings,omitempty"`
	Error    *wikifier.Warning  `json:"parse_error,omitempty"`
}

// DisplayFile returns the display result for a plain text file.
func (w *Wiki) DisplayFile(path string) interface{} {
	var r DisplayFile
	path = filepath.FromSlash(path) // just in case

	// ensure it can be made relative to dir.wiki
	relPath := w.RelPath(path)
	if relPath == "" {
		return DisplayError{
			Error:         "Bad filepath",
			DetailedError: "File '" + path + "' cannot be made relative to '" + w.Opt.Dir.Wiki + "'",
		}
	}

	// file does not exist or can't be read
	fi, err := os.Lstat(path)
	if err != nil {
		return DisplayError{
			Error:         "File does not exist.",
			DetailedError: "File '" + path + "' error: " + err.Error(),
		}
	}

	// read file
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return DisplayError{
			Error:         "Error reading file.",
			DetailedError: "File '" + path + "' error: " + err.Error(),
		}
	}

	// results
	mod := fi.ModTime()
	r.File = filepath.ToSlash(relPath)
	r.Path = path
	r.Modified = &mod
	r.Content = string(content)

	// For pages/models only-- check for cached warnings and errors
	if rel := w._relPath(path, w.Dir("pages")); rel != "" {
		res := w.DisplayPageDraft(rel, true)
		if dispPage, ok := res.(DisplayPage); ok {
			// extract warnings/etc from a DisplayPage
			r.Warnings = dispPage.Warnings

		} else if dispErr, ok := res.(DisplayError); ok && dispErr.ParseError != nil {
			// extract parsing error from a DisplayError
			r.Error = &wikifier.Warning{
				Message:  dispErr.ParseError.Err.Error(),
				Position: dispErr.ParseError.Position,
			}
		}
	} else if rel := w._relPath(path, w.Dir("models")); rel != "" {
		// TODO: model errors/ warnings
	}

	return r
}

func (w *Wiki) checkDirectories() {
	// TODO
	panic("unimplemented")
}

// RelPath takes an absolute file path and attempts to make it relative
// to the wiki directory, regardless of whether the path exists.
//
// If the path can be made relative without following symlinks, this is
// preferred. If that fails, symlinks in absPath are followed and a
// second attempt is made.
//
// In any case the path cannot be made relative to the wiki directory,
// an empty string is returned.
func (w *Wiki) RelPath(absPath string) string {
	rel := w._relPath(absPath, w.Dir())
	if strings.Contains(rel, ".."+string(os.PathSeparator)) {
		return ""
	}
	if strings.Contains(rel, string(os.PathSeparator)+"..") {
		return ""
	}
	return rel
}

func (w *Wiki) _relPath(absPath, base string) string {

	// can't resolve wiki path
	if base == "" {
		return ""
	}

	// made it relative as-is
	if rel, err := filepath.Rel(base, absPath); err == nil {
		return rel
	}

	// try to make it relative by resolving absPath as absolute
	absPath, _ = filepath.Abs(absPath)
	if absPath != "" {
		if rel, err := filepath.Rel(base, absPath); err == nil {
			return rel
		}
	}

	return ""
}

func (w *Wiki) allPageFiles() []string {
	files, _ := wikifier.UniqueFilesInDir(w.Opt.Dir.Page, []string{"page", "md"}, false)
	return files
}

func (w *Wiki) allCategoryFiles(catType CategoryType) []string {
	dir := w.Opt.Dir.Category
	if catType != "" {
		dir = filepath.Join(dir, string(catType))
	}
	files, _ := wikifier.UniqueFilesInDir(dir, []string{"cat"}, false)
	return files
}

func (w *Wiki) allModelFiles() []string {
	files, _ := wikifier.UniqueFilesInDir(w.Opt.Dir.Model, []string{"model"}, false)
	return files
}

func (w *Wiki) allImageFiles() []string {
	files, _ := wikifier.UniqueFilesInDir(w.Opt.Dir.Image, []string{"png", "jpg", "jpeg"}, false)
	return files
}

// pathForPage returns the absolute path for a page.
func (w *Wiki) pathForPage(pageName string) string {

	// try lowercased version first (quiki style)
	lcPageName := filepath.FromSlash(wikifier.PageName(pageName))
	path, _ := filepath.Abs(filepath.Join(w.Opt.Dir.Page, lcPageName))

	// it doesn't exist; try non-lowercased version (markdown/etc)
	if _, err := os.Stat(path); err != nil {
		normalPageName := filepath.FromSlash(wikifier.PageName(pageName))
		normalPath, _ := filepath.Abs(filepath.Join(w.Opt.Dir.Page, normalPageName))
		if _, err := os.Stat(normalPath); err == nil {
			return normalPath
		}
	}

	return path
}

// pathForCategory returns the absolute path for a category. If necessary, it
// creates directories for the path components that do not exist.
func (w *Wiki) pathForCategory(catName string, catType CategoryType, createOK bool) string {
	catName = wikifier.CategoryName(catName)
	dir := filepath.Join(w.Opt.Dir.Cache, "category")
	if createOK {
		wikifier.MakeDir(dir, filepath.Join(string(catType), catName))
	}
	path, _ := filepath.Abs(filepath.Join(dir, string(catType), catName))
	return path
}

// pathForImage returns the absolute path for an image.
func (w *Wiki) pathForImage(imageName string) string {
	path, _ := filepath.Abs(filepath.Join(w.Opt.Dir.Image, filepath.FromSlash(imageName)))
	return path
}

// pathForModel returns the absolute path for a model.
func (w *Wiki) pathForModel(modelName string) string {
	modelName = wikifier.PageNameExt(modelName, ".model")
	path, _ := filepath.Abs(filepath.Join(w.Opt.Dir.Model, filepath.FromSlash(modelName)))
	return path
}

// Dir returns the absolute path to the resolved wiki directory.
// If the wiki directory is a symlink, it is followed.
//
// Optional path components can be passed as arguments to be joined
// with the wiki root by the path separator.
func (w *Wiki) Dir(dirs ...string) string {
	wikiAbs, _ := filepath.Abs(w.Opt.Dir.Wiki)
	return filepath.Join(append([]string{wikiAbs}, dirs...)...)
}

// UnresolvedAbsFilePath takes a relative path to a file within the wiki
// (e.g. `pages/mypage.page`) and joins it with the absolute path to the wiki
// directory. The result is an absolute path which may or may not exist.
//
// Symlinks are not followed. If that is desired, use absoluteFilePath instead.
//
func (w *Wiki) UnresolvedAbsFilePath(relPath string) string {

	// sanitize
	relPath = filepath.FromSlash(relPath)

	// join with wiki dir
	path := w.Dir(relPath)

	// resolve symlink
	abs, _ := filepath.Abs(path)
	return abs
}

// AbsFilePath takes a relative path to a file within the wiki
// (e.g. `pages/mypage.page`), joins it with the wiki directory, and evaluates it
// with `filepath.Abs()`. The result is an absolute path which may or may not exist.
//
// If the file is a symlink, it is followed. Thus, it is possible for the resulting
// path to exist outside the wiki directory. If that is not desired, use
// unresolvedAbsFilePath instead.
func (w *Wiki) AbsFilePath(relPath string) string {
	path, _ := filepath.Abs(w.Dir(w.UnresolvedAbsFilePath(relPath)))
	return path
}
<commit_msg>do not include metacategories in (wiki).Categories()<commit_after>package wiki

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/cooper/quiki/wikifier"
)

// DisplayFile represents a plain text file to display.
type DisplayFile struct {

	// file name relative to wiki root.
	// path delimiter '/' is always used, regardless of OS.
	File string `json:"file,omitempty"`

	// absolute file path of the file.
	// OS-specific path delimiter is used.
	Path string `json:"path,omitempty"`

	// the plain text file content
	Content string `json:"-"`

	// time when the file was last modified
	Modified *time.Time `json:"modified,omitempty"`

	// for pages/models/etc, parser warnings and error
	Warnings []wikifier.Warning `json:"parse_warnings,omitempty"`
	Error    *wikifier.Warning  `json:"parse_error,omitempty"`
}

// DisplayFile returns the display result for a plain text file.
func (w *Wiki) DisplayFile(path string) interface{} {
	var r DisplayFile
	path = filepath.FromSlash(path) // just in case

	// ensure it can be made relative to dir.wiki
	relPath := w.RelPath(path)
	if relPath == "" {
		return DisplayError{
			Error:         "Bad filepath",
			DetailedError: "File '" + path + "' cannot be made relative to '" + w.Opt.Dir.Wiki + "'",
		}
	}

	// file does not exist or can't be read
	fi, err := os.Lstat(path)
	if err != nil {
		return DisplayError{
			Error:         "File does not exist.",
			DetailedError: "File '" + path + "' error: " + err.Error(),
		}
	}

	// read file
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return DisplayError{
			Error:         "Error reading file.",
			DetailedError: "File '" + path + "' error: " + err.Error(),
		}
	}

	// results
	mod := fi.ModTime()
	r.File = filepath.ToSlash(relPath)
	r.Path = path
	r.Modified = &mod
	r.Content = string(content)

	// For pages/models only-- check for cached warnings and errors
	if rel := w._relPath(path, w.Dir("pages")); rel != "" {
		res := w.DisplayPageDraft(rel, true)
		if dispPage, ok := res.(DisplayPage); ok {
			// extract warnings/etc from a DisplayPage
			r.Warnings = dispPage.Warnings

		} else if dispErr, ok := res.(DisplayError); ok && dispErr.ParseError != nil {
			// extract parsing error from a DisplayError
			r.Error = &wikifier.Warning{
				Message:  dispErr.ParseError.Err.Error(),
				Position: dispErr.ParseError.Position,
			}
		}
	} else if rel := w._relPath(path, w.Dir("models")); rel != "" {
		// TODO: model errors/ warnings
	}

	return r
}

func (w *Wiki) checkDirectories() {
	// TODO
	panic("unimplemented")
}

// RelPath takes an absolute file path and attempts to make it relative
// to the wiki directory, regardless of whether the path exists.
//
// If the path can be made relative without following symlinks, this is
// preferred. If that fails, symlinks in absPath are followed and a
// second attempt is made.
//
// In any case the path cannot be made relative to the wiki directory,
// an empty string is returned.
func (w *Wiki) RelPath(absPath string) string {
	rel := w._relPath(absPath, w.Dir())
	if strings.Contains(rel, ".."+string(os.PathSeparator)) {
		return ""
	}
	if strings.Contains(rel, string(os.PathSeparator)+"..") {
		return ""
	}
	return rel
}

func (w *Wiki) _relPath(absPath, base string) string {

	// can't resolve wiki path
	if base == "" {
		return ""
	}

	// made it relative as-is
	if rel, err := filepath.Rel(base, absPath); err == nil {
		return rel
	}

	// try to make it relative by resolving absPath as absolute
	absPath, _ = filepath.Abs(absPath)
	if absPath != "" {
		if rel, err := filepath.Rel(base, absPath); err == nil {
			return rel
		}
	}

	return ""
}

func (w *Wiki) allPageFiles() []string {
	files, _ := wikifier.UniqueFilesInDir(w.Opt.Dir.Page, []string{"page", "md"}, false)
	return files
}

func (w *Wiki) allCategoryFiles(catType CategoryType) []string {
	dir := w.Opt.Dir.Category
	if catType != "" {
		dir = filepath.Join(dir, string(catType))
	}
	// consider: once we supported prefixed categories (issue #79),
	// will need to rethink the below...
	files, _ := wikifier.UniqueFilesInDir(dir, []string{"cat"}, true)
	return files
}

func (w *Wiki) allModelFiles() []string {
	files, _ := wikifier.UniqueFilesInDir(w.Opt.Dir.Model, []string{"model"}, false)
	return files
}

func (w *Wiki) allImageFiles() []string {
	files, _ := wikifier.UniqueFilesInDir(w.Opt.Dir.Image, []string{"png", "jpg", "jpeg"}, false)
	return files
}

// pathForPage returns the absolute path for a page.
func (w *Wiki) pathForPage(pageName string) string {

	// try lowercased version first (quiki style)
	lcPageName := filepath.FromSlash(wikifier.PageName(pageName))
	path, _ := filepath.Abs(filepath.Join(w.Opt.Dir.Page, lcPageName))

	// it doesn't exist; try non-lowercased version (markdown/etc)
	if _, err := os.Stat(path); err != nil {
		normalPageName := filepath.FromSlash(wikifier.PageName(pageName))
		normalPath, _ := filepath.Abs(filepath.Join(w.Opt.Dir.Page, normalPageName))
		if _, err := os.Stat(normalPath); err == nil {
			return normalPath
		}
	}

	return path
}

// pathForCategory returns the absolute path for a category. If necessary, it
// creates directories for the path components that do not exist.
func (w *Wiki) pathForCategory(catName string, catType CategoryType, createOK bool) string {
	catName = wikifier.CategoryName(catName)
	dir := filepath.Join(w.Opt.Dir.Cache, "category")
	if createOK {
		wikifier.MakeDir(dir, filepath.Join(string(catType), catName))
	}
	path, _ := filepath.Abs(filepath.Join(dir, string(catType), catName))
	return path
}

// pathForImage returns the absolute path for an image.
func (w *Wiki) pathForImage(imageName string) string {
	path, _ := filepath.Abs(filepath.Join(w.Opt.Dir.Image, filepath.FromSlash(imageName)))
	return path
}

// pathForModel returns the absolute path for a model.
func (w *Wiki) pathForModel(modelName string) string {
	modelName = wikifier.PageNameExt(modelName, ".model")
	path, _ := filepath.Abs(filepath.Join(w.Opt.Dir.Model, filepath.FromSlash(modelName)))
	return path
}

// Dir returns the absolute path to the resolved wiki directory.
// If the wiki directory is a symlink, it is followed.
//
// Optional path components can be passed as arguments to be joined
// with the wiki root by the path separator.
func (w *Wiki) Dir(dirs ...string) string {
	wikiAbs, _ := filepath.Abs(w.Opt.Dir.Wiki)
	return filepath.Join(append([]string{wikiAbs}, dirs...)...)
}

// UnresolvedAbsFilePath takes a relative path to a file within the wiki
// (e.g. `pages/mypage.page`) and joins it with the absolute path to the wiki
// directory. The result is an absolute path which may or may not exist.
//
// Symlinks are not followed. If that is desired, use absoluteFilePath instead.
//
func (w *Wiki) UnresolvedAbsFilePath(relPath string) string {

	// sanitize
	relPath = filepath.FromSlash(relPath)

	// join with wiki dir
	path := w.Dir(relPath)

	// resolve symlink
	abs, _ := filepath.Abs(path)
	return abs
}

// AbsFilePath takes a relative path to a file within the wiki
// (e.g. `pages/mypage.page`), joins it with the wiki directory, and evaluates it
// with `filepath.Abs()`. The result is an absolute path which may or may not exist.
//
// If the file is a symlink, it is followed. Thus, it is possible for the resulting
// path to exist outside the wiki directory. If that is not desired, use
// unresolvedAbsFilePath instead.
func (w *Wiki) AbsFilePath(relPath string) string {
	path, _ := filepath.Abs(w.Dir(w.UnresolvedAbsFilePath(relPath)))
	return path
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/pkg/errors"
	"log"
	"math/rand"
	"net"
	"os"
	"strings"
	"time"
)

const road_width = 40
const road_lenght = 20
const car_width = 16
const car_lenght = 7
const result_width = 75

type Config struct {
	Log                 *log.Logger
	AcidPath, ScorePath string
}

type Point struct {
	X, Y int
}

type GameData struct {
	playerName                string
	carPosition, bombPosition Point
	roads                     [][]byte
	car, clear, gameOver      []byte
	gameStarted               int64
}

func generateRoad(reverse bool) []byte {
	road := make([]byte, road_width*road_lenght)
	midline := reverse
	for row := 0; row < road_lenght; row++ {
		for column := 0; column < road_width; column++ {
			var symbol byte
			if column == 0 || column == road_width-2 {
				symbol = byte('|')
			} else if column == road_width-1 {
				symbol = byte('\n')
			} else if column == road_width/2-1 {
				if midline {
					symbol = byte('|')
				} else {
					symbol = byte(' ')
				}
				midline = !midline
			} else {
				symbol = byte(' ')
			}
			road[row*road_width+column] = symbol
		}

	}
	return road
}

func getAcid(conf *Config, fileName string) []byte {
	fileStat, err := os.Stat(conf.AcidPath + "/" + fileName)
	if err != nil {
		conf.Log.Printf("Acid %s does not exist: %v\n", fileName, err)
	}

	acid := make([]byte, fileStat.Size())
	f, err := os.OpenFile(conf.AcidPath+"/"+fileName, os.O_RDONLY, os.ModePerm)
	if err != nil {
		conf.Log.Printf("Error while opening %s: %v\n", fileName, err)
		os.Exit(1)
	}
	defer f.Close()

	f.Read(acid)

	return acid
}

func updatePosition(conn net.Conn, position *Point) {
	for {
		direction := make([]byte, 1)

		_, err := conn.Read(direction)
		if err != nil {
			return
		}

		switch direction[0] {
		case 68:
			// Left
			position.X--
		case 67:
			// Right
			position.X++
		case 65:
			// Up
			position.Y--
		case 66:
			// Down
			position.Y++
		}
	}
}

func readName(conf *Config, conn net.Conn) (string, error) {
	conn.Write([]byte("Enter your name:"))
	io := bufio.NewReader(conn)
	name, err := io.ReadString('\n')
	if err != nil {
		conf.Log.Println("Error while name reading", err)
		return "", err
	}
	if name == "" {
		conf.Log.Println("Empty name")
		return "", errors.New("Empty name")
	}
	if len(name) > result_width/2 {
		conf.Log.Println("Too long name")
		return "", errors.New("Too long name")
	}
	return strings.Replace(name, "\n", "", -1), nil
}

func gameOver(conf *Config, conn net.Conn, gameData *GameData) {
	diff := fmt.Sprintf("%d", time.Now().Unix()-gameData.gameStarted)

	//Name
	for i, char := range []byte(gameData.playerName) {
		gameData.gameOver[i] = char
	}
	//:
	gameData.gameOver[result_width/2] = byte(':')
	// Score
	for i := range diff {
		gameData.gameOver[result_width-len(diff)+i] = byte(diff[i])

	}
	conn.Write(gameData.clear)
	conn.Write(gameData.gameOver)

}

func handleRequest(conf *Config, conn net.Conn) {
	defer conn.Close()

	gameData := GameData{}
	gameData.carPosition = Point{12, 12}
	gameData.bombPosition = Point{road_width, road_lenght}

	gameData.roads = [][]byte{generateRoad(false), generateRoad(true)}
	gameData.car = getAcid(conf, "car.txt")
	gameData.clear = getAcid(conf, "clear.txt")
	gameData.gameOver = getAcid(conf, "game_over.txt")

	name, err := readName(conf, conn)
	if err != nil {
		return
	}
	gameData.playerName = name
	gameData.gameStarted = time.Now().Unix()

	go updatePosition(conn, &gameData.carPosition)

	for {
		if gameData.carPosition.X < 1 || gameData.carPosition.X > 23 || gameData.carPosition.Y < 1 || gameData.carPosition.Y > 12 {
			// Hit the wall
			gameOver(conf, conn, &gameData)
			return
		} else if gameData.carPosition.X <= gameData.bombPosition.X && gameData.carPosition.X+car_width-1 > gameData.bombPosition.X &&
			gameData.carPosition.Y < gameData.bombPosition.Y && gameData.carPosition.Y+car_lenght-1 > gameData.bombPosition.Y {
			// Hit the bomb
			gameOver(conf, conn, &gameData)
			return
		}

		for i := range gameData.roads {
			data := make([]byte, len(gameData.roads[i]))
			copy(data, gameData.roads[i])

			// Moving cursor at the beginning
			_, err := conn.Write(gameData.clear)
			if err != nil {
				return
			}

			// Applying the bomb
			if gameData.bombPosition.Y < road_lenght {
				data[gameData.bombPosition.Y*road_width+gameData.bombPosition.X] = byte('X')
				gameData.bombPosition.Y++
			} else if rand.Int()%3 == 0 {
				gameData.bombPosition.X, gameData.bombPosition.Y = rand.Intn(road_width-3)+1, 0
			}

			// Applying the car
			for line := 0; line < 7; line++ {
				copy(data[((gameData.carPosition.Y+line)*road_width+gameData.carPosition.X):((gameData.carPosition.Y+line)*road_width+gameData.carPosition.X)+15], gameData.car[line*car_width:line*car_width+15])
			}

			_, err = conn.Write(data)
			if err != nil {
				return
			}
			time.Sleep(200 * time.Millisecond)
		}
	}
}

func main() {
	var logFile string
	conf := &Config{}

	flag.StringVar(&logFile, "l", "/var/log/race.log", "Log file")
	flag.StringVar(&conf.AcidPath, "a", "/Users/leoleovich/go/src/github.com/leoleovich/race/artifacts", "Artifacts location")
	flag.StringVar(&conf.AcidPath, "s", "/Users/leoleovich/go/src/github.com/leoleovich/race/artifacts", "Score location")
	flag.Parse()

	logfile, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
	conf.Log = log.New(logfile, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)

	l, err := net.Listen("tcp", ":4242")
	if err != nil {
		conf.Log.Println(err)
		os.Exit(2)
	}
	defer l.Close()

	for {
		conn, err := l.Accept()
		if err != nil {
			conf.Log.Println("Failed to accept request", err)
		}
		go handleRequest(conf, conn)
	}
}
<commit_msg>Bomb factor<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/pkg/errors"
	"log"
	"math/rand"
	"net"
	"os"
	"strings"
	"time"
)

const road_width = 40
const road_lenght = 20
const Car_width = 16
const Car_lenght = 7
const result_width = 75

type Config struct {
	Log                 *log.Logger
	AcidPath, ScorePath string
}

type Point struct {
	X, Y int
}

type GameData struct {
	PlayerName                string
	CarPosition, bombPosition Point
	Roads                     [][]byte
	Car, Clear, GameOver      []byte
	Score		          int64
	BombFactor                int
}

func generateRoad(reverse bool) []byte {
	road := make([]byte, road_width*road_lenght)
	midline := reverse
	for row := 0; row < road_lenght; row++ {
		for column := 0; column < road_width; column++ {
			var symbol byte
			if column == 0 || column == road_width-2 {
				symbol = byte('|')
			} else if column == road_width-1 {
				symbol = byte('\n')
			} else if column == road_width/2-1 {
				if midline {
					symbol = byte('|')
				} else {
					symbol = byte(' ')
				}
				midline = !midline
			} else {
				symbol = byte(' ')
			}
			road[row*road_width+column] = symbol
		}

	}
	return road
}

func getAcid(conf *Config, fileName string) []byte {
	fileStat, err := os.Stat(conf.AcidPath + "/" + fileName)
	if err != nil {
		conf.Log.Printf("Acid %s does not exist: %v\n", fileName, err)
	}

	acid := make([]byte, fileStat.Size())
	f, err := os.OpenFile(conf.AcidPath+"/"+fileName, os.O_RDONLY, os.ModePerm)
	if err != nil {
		conf.Log.Printf("Error while opening %s: %v\n", fileName, err)
		os.Exit(1)
	}
	defer f.Close()

	f.Read(acid)

	return acid
}

func updatePosition(conn net.Conn, position *Point) {
	for {
		direction := make([]byte, 1)

		_, err := conn.Read(direction)
		if err != nil {
			return
		}

		switch direction[0] {
		case 68:
			// Left
			position.X--
		case 67:
			// Right
			position.X++
		case 65:
			// Up
			position.Y--
		case 66:
			// Down
			position.Y++
		}
	}
}

func updateScore(Score *int64) {
	for {
		*Score++
		time.Sleep(200 * time.Millisecond)
	}

}

func readName(conf *Config, conn net.Conn) (string, error) {
	conn.Write([]byte("Enter your name:"))
	io := bufio.NewReader(conn)
	name, err := io.ReadString('\n')
	if err != nil {
		conf.Log.Println("Error while name reading", err)
		return "", err
	}
	if name == "" {
		conf.Log.Println("Empty name")
		return "", errors.New("Empty name")
	}
	if len(name) > result_width/2 {
		conf.Log.Println("Too long name")
		return "", errors.New("Too long name")
	}
	return strings.Replace(name, "\n", "", -1), nil
}

func gameOver(conf *Config, conn net.Conn, gameData *GameData) {
	diff := fmt.Sprintf("%d", gameData.Score)

	//Name
	for i, char := range []byte(gameData.PlayerName) {
		gameData.GameOver[i] = char
	}
	//:
	gameData.GameOver[result_width/2] = byte(':')
	// Score
	for i := range diff {
		gameData.GameOver[result_width-len(diff)+i] = byte(diff[i])

	}
	conn.Write(gameData.Clear)
	conn.Write(gameData.GameOver)

}

func handleRequest(conf *Config, conn net.Conn) {
	defer conn.Close()

	gameData := GameData{}
	gameData.CarPosition = Point{12, 12}
	gameData.bombPosition = Point{road_width, road_lenght}
	gameData.BombFactor = 3
	gameData.Speed = 200

	gameData.Roads = [][]byte{generateRoad(false), generateRoad(true)}
	gameData.Car = getAcid(conf, "Car.txt")
	gameData.Clear = getAcid(conf, "Clear.txt")
	gameData.GameOver = getAcid(conf, "game_over.txt")

	name, err := readName(conf, conn)
	if err != nil {
		return
	}
	gameData.PlayerName = name
	go updateScore(&gameData.Score)
	go updatePosition(conn, &gameData.CarPosition)

	for {
		if gameData.CarPosition.X < 1 || gameData.CarPosition.X > 23 || gameData.CarPosition.Y < 1 || gameData.CarPosition.Y > 12 {
			// Hit the wall
			gameOver(conf, conn, &gameData)
			return
		} else if gameData.CarPosition.X <= gameData.bombPosition.X && gameData.CarPosition.X+Car_width-1 > gameData.bombPosition.X &&
			gameData.CarPosition.Y < gameData.bombPosition.Y && gameData.CarPosition.Y+Car_lenght-1 > gameData.bombPosition.Y {
			// Hit the bomb
			gameOver(conf, conn, &gameData)
			return
		}

		for i := range gameData.Roads {
			data := make([]byte, len(gameData.Roads[i]))
			copy(data, gameData.Roads[i])

			// Moving cursor at the beginning
			_, err := conn.Write(gameData.Clear)
			if err != nil {
				return
			}

			// Checking and updating complexity
			if gameData.Score > 100 && gameData.Score < 500 {
				gameData.BombFactor = 2
			} else if gameData.Score >= 500 {
				gameData.BombFactor = 1
			}

			// Applying the bomb
			if gameData.bombPosition.Y < road_lenght {
				data[gameData.bombPosition.Y*road_width+gameData.bombPosition.X] = byte('X')
				gameData.bombPosition.Y++
			} else if rand.Int()%gameData.BombFactor == 0 {
				gameData.bombPosition.X, gameData.bombPosition.Y = rand.Intn(road_width-3)+1, 0
			}

			// Applying the Car
			for line := 0; line < 7; line++ {
				copy(data[((gameData.CarPosition.Y+line)*road_width+gameData.CarPosition.X):((gameData.CarPosition.Y+line)*road_width+gameData.CarPosition.X)+15], gameData.Car[line*Car_width:line*Car_width+15])
			}

			_, err = conn.Write(data)
			if err != nil {
				return
			}
			time.Sleep(200 * time.Millisecond)
		}
	}
}

func main() {
	var logFile string
	conf := &Config{}

	flag.StringVar(&logFile, "l", "/var/log/race.log", "Log file")
	flag.StringVar(&conf.AcidPath, "a", "/Users/leoleovich/go/src/github.com/leoleovich/race/artifacts", "Artifacts location")
	flag.StringVar(&conf.AcidPath, "s", "/Users/leoleovich/go/src/github.com/leoleovich/race/artifacts", "Score location")
	flag.Parse()

	logfile, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
	conf.Log = log.New(logfile, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)

	l, err := net.Listen("tcp", ":4242")
	if err != nil {
		conf.Log.Println(err)
		os.Exit(2)
	}
	defer l.Close()

	for {
		conn, err := l.Accept()
		if err != nil {
			conf.Log.Println("Failed to accept request", err)
		}
		go handleRequest(conf, conn)
	}
}
<|endoftext|>


<commit_before>// This is the primary file of LibreJS-Gopher

package librejsgopher

import (
	"errors"
	"io/ioutil"
	"strings"
)

var LicensesCapitalizedStrings []string // An array of strings where each string is a license name or sub-string that needs to be capitalized

var LicenseMap map[string]string // LicenseMap is a map of license names to magnet URLs

func init() {
	LicensesCapitalizedStrings = []string{"BSD", "CC", "GPL", "ISC", "MPL"}

	LicenseMap = map[string]string{
		"AGPL-3.0":      "magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt",
		"Apache-2.0":    "magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt",
		"Artistic-2.0":  "magnet:?xt=urn:btih:54fd2283f9dbdf29466d2df1a98bf8f65cafe314&dn=artistic-2.0.txt",
		"BSD-3.0":       "magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt",
		"CC0":           "magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt",
		"Expat":         "magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt",
		"FreeBSD":       "magnet:?xt=urn:btih:87f119ba0b429ba17a44b4bffcab33165ebdacc0&dn=freebsd.txt",
		"GPL-2.0":       "magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt",
		"GPL-3.0":       "magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt",
		"ISC":           "magnet:?xt=urn:btih:b8999bbaf509c08d127678643c515b9ab0836bae&dn=ISC.txt",
		"LGPL-2.1":      "magnet:?xt=urn:btih:5de60da917303dbfad4f93fb1b985ced5a89eac2&dn=lgpl-2.1.txt",
		"LGPL-3.0":      "magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt",
		"MPL-2.0":       "magnet:?xt=urn:btih:3877d6d54b3accd4bc32f8a48bf32ebc0901502a&dn=mpl-2.0.txt",
		"Public-Domain": "magnet:?xt=urn:btih:e95b018ef3580986a04669f1b5879592219e2a7a&dn=public-domain.txt",
		"X11":           "magnet:?xt=urn:btih:5305d91886084f776adcf57509a648432709a7c7&dn=x11.txt",
		"XFree86":       "magnet:?xt=urn:btih:12f2ec9e8de2a3b0002a33d518d6010cc8ab2ae9&dn=xfree86.txt",
	}
}

// GetFileLicense
// This function will get the license of the file, assuming it uses a valid LibreJS short-form header.
func GetFileLicense(file string) (LibreJSMetaInfo, error) {
	var getError error
	var metaInfo LibreJSMetaInfo

	fileContentBytes, fileReadError := ioutil.ReadFile(file) // Get the fileContent or if the file does not exist (or we do not have the permission) assign to fileReadError

	if fileReadError == nil { // If there was no read error
		fileContent := string(fileContentBytes[:])           // Convert to string
		fileContentLines := strings.Split(fileContent, "\n") // Split each new line into an []string
		fileContentLinesCount := len(fileContentLines)

		if fileContentLinesCount > 1 { // If this file is not a single line or empty
			fileLineParserChannel := make(chan LibreJSMetaInfo) // Make a channel that takes LibreJSMetaInfo
			linesParsed := 0                                    // Define linesParsed as the number of lines parsed by FileLicenseLineParser

			for _, lineContent := range fileContentLines { // For each license
				go FileLicenseLineParser(fileLineParserChannel, lineContent) // Asynchronously call FileLicenseLineParser
			}

		LineParserLoop:
			for libreJsMetaInfo := range fileLineParserChannel { // Constantly listen for channel input
				var endChannelListening bool

				linesParsed++ // Add one two linesParsed

				if libreJsMetaInfo.License != "" { // If the provided LibreJSMetaInfo has a valid License
					metaInfo = libreJsMetaInfo // Assign metaInfo as provided libreJsMetaInfo
					endChannelListening = true
				}

				if (fileContentLinesCount == linesParsed) || (endChannelListening) { // If we have parsed all lines or found the header info
					close(fileLineParserChannel) // Close the channel
					break LineParserLoop         // Break the loop
				}
			}

			if metaInfo.License == "" { // If there is no License defined by the end of the file
				getError = errors.New("LibreJS short-form header does not exist in this file.")
			}
		} else { // If the length of the file is 1 line or none
			getError = errors.New("File is either empty or does not contain the necessary individual lines required by LibreJS short-form blocks.")
		}
	} else { // If the file does not exist
		getError = errors.New(file + " does not exist.")
	}

	return metaInfo, getError
}

// FileLicenseLineParser
// This function handles individual line parsing
func FileLicenseLineParser(returnContentChannel chan LibreJSMetaInfo, lineContent string) {
	metaInfo := LibreJSMetaInfo{}

	lineContent = strings.Replace(lineContent, "//", "", -1) // Replace any // with nothing
	lineContent = strings.Replace(lineContent, "*", "", -1)  // Replace any * (block quotes) with nothing
	lineContent = strings.TrimPrefix(lineContent, " ")       // Trim any prefixed whitespace

	if strings.HasPrefix(lineContent, "@license") { // If the line starts with @license
		licenseHeaderFragments := strings.SplitN(lineContent, " ", 3)  // Split the license header info into three segments, separated by whitespace
		metaInfo.License = ParseLicenseName(licenseHeaderFragments[2]) // Define License as the parsed license name of the last item in fragments index
		metaInfo.Magnet = licenseHeaderFragments[1]                    // Define Magnet as the second item in the fragments index
	}

	returnContentChannel <- metaInfo
}

// GetMagnetLink
// This function will get a magnet link of the associated license exists
// Returns string for magnet link, error if item does not exist
func GetMagnetLink(license string) (string, error) {
	var magnetLinkFetchError error

	license = ParseLicenseName(license) // Parse the license name first
	magnetURL, licenseExists := LicenseMap[license]

	if !licenseExists { // If the license does not exist
		magnetLinkFetchError = errors.New(license + " does not exist.")
	}

	return magnetURL, magnetLinkFetchError
}

// ParseLicenseName
// This function will attempt to parse the provided license into a more logic naming scheme used in LicenseMap
func ParseLicenseName(license string) string {
	license = strings.ToLower(license) // Lowercase the entire string to make selective capitalization easier

	for _, licenseCapitalizedString := range LicensesCapitalizedStrings { // For each capitalized string of a license in LicensesCapitalizedStrings
		license = strings.Replace(license, strings.ToLower(licenseCapitalizedString), licenseCapitalizedString, -1) // Replace any lowercase instance with capitalized instance
	}

	license = strings.ToTitle(license)               // Title the license (example: apache -> Apache)
	license = strings.Replace(license, " ", "-", -1) // Replace whitespacing with hyphens

	return license
}
<commit_msg>Added AddLicenseInfo function which will add a LibreJS short-form header and footer of the requested license and license magnet URL to the requested file content.<commit_after>// This is the primary file of LibreJS-Gopher

package librejsgopher

import (
	"errors"
	"io/ioutil"
	"os"
	"strings"
)

var LicensesCapitalizedStrings []string // An array of strings where each string is a license name or sub-string that needs to be capitalized

var LicenseMap map[string]string // LicenseMap is a map of license names to magnet URLs

func init() {
	LicensesCapitalizedStrings = []string{"BSD", "CC", "GPL", "ISC", "MPL"}

	LicenseMap = map[string]string{
		"AGPL-3.0":      "magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt",
		"Apache-2.0":    "magnet:?xt=urn:btih:8e4f440f4c65981c5bf93c76d35135ba5064d8b7&dn=apache-2.0.txt",
		"Artistic-2.0":  "magnet:?xt=urn:btih:54fd2283f9dbdf29466d2df1a98bf8f65cafe314&dn=artistic-2.0.txt",
		"BSD-3.0":       "magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt",
		"CC0":           "magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt",
		"Expat":         "magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt",
		"FreeBSD":       "magnet:?xt=urn:btih:87f119ba0b429ba17a44b4bffcab33165ebdacc0&dn=freebsd.txt",
		"GPL-2.0":       "magnet:?xt=urn:btih:cf05388f2679ee054f2beb29a391d25f4e673ac3&dn=gpl-2.0.txt",
		"GPL-3.0":       "magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt",
		"ISC":           "magnet:?xt=urn:btih:b8999bbaf509c08d127678643c515b9ab0836bae&dn=ISC.txt",
		"LGPL-2.1":      "magnet:?xt=urn:btih:5de60da917303dbfad4f93fb1b985ced5a89eac2&dn=lgpl-2.1.txt",
		"LGPL-3.0":      "magnet:?xt=urn:btih:0ef1b8170b3b615170ff270def6427c317705f85&dn=lgpl-3.0.txt",
		"MPL-2.0":       "magnet:?xt=urn:btih:3877d6d54b3accd4bc32f8a48bf32ebc0901502a&dn=mpl-2.0.txt",
		"Public-Domain": "magnet:?xt=urn:btih:e95b018ef3580986a04669f1b5879592219e2a7a&dn=public-domain.txt",
		"X11":           "magnet:?xt=urn:btih:5305d91886084f776adcf57509a648432709a7c7&dn=x11.txt",
		"XFree86":       "magnet:?xt=urn:btih:12f2ec9e8de2a3b0002a33d518d6010cc8ab2ae9&dn=xfree86.txt",
	}
}

// AddLicenseInfo
// This function will add a valid LibreJS short-form header and footer to the file. You can set to write the file automatically (we will always return new file content or an error)
func AddLicenseInfo(license string, file string, writeContentAutomatically bool) (string, error) {
	var newFileContent string
	var addError error

	if strings.HasSuffix(file, ".js") { // If this is a JavaScript file
		fileContentBytes, fileReadError := ioutil.ReadFile(file) // Get the fileContent or if the file does not exist (or we do not have the permission) assign to fileReadError

		if fileReadError == nil { // If there was no read error
			parsedLicense := ParseLicenseName(license)             // Format license to be consistent when appending to newFileContent
			magnetURL, magnetError := GetMagnetLink(parsedLicense) // Attempt to get the magnet URL and if license does not exist return error

			if magnetError == nil { // If the license requested is valid and return a magnet URL
				fileContentString := string(fileContentBytes[:])                                                             // Convert to string
				newFileContent = "@license " + magnetURL + " " + parsedLicense + "\n" + fileContentString + "\n@license-end" // Add @license INFO + content + @license-end

				if writeContentAutomatically { // If we should write the file content automatically
					fileStruct, _ := os.Open(file)   // Open the file and get an os.File struct
					fileStat, _ := fileStruct.Stat() // Get the stats about the file
					fileMode := fileStat.Mode()
					fileStruct.Close() // Close the open file struct

					ioutil.WriteFile(file, []byte(newFileContent), fileMode) // Write the file with the new content and same mode
				}
			} else { // If the magnetURL does not exist
				addError = magnetError // Assign addError as the magnetError
			}
		} else { // If there was a read error
			addError = errors.New(file + " does not exist.")
		}
	} else { // File provided is not a JavaScript file
		addError = errors.New(file + " is not a JavaScript file (detected if ending with .js).")
	}

	return newFileContent, addError
}

// GetFileLicense
// This function will get the license of the file, assuming it uses a valid LibreJS short-form header.
func GetFileLicense(file string) (LibreJSMetaInfo, error) {
	var getError error
	var metaInfo LibreJSMetaInfo

	fileContentBytes, fileReadError := ioutil.ReadFile(file) // Get the fileContent or if the file does not exist (or we do not have the permission) assign to fileReadError

	if fileReadError == nil { // If there was no read error
		fileContent := string(fileContentBytes[:])           // Convert to string
		fileContentLines := strings.Split(fileContent, "\n") // Split each new line into an []string
		fileContentLinesCount := len(fileContentLines)

		if fileContentLinesCount > 1 { // If this file is not a single line or empty
			fileLineParserChannel := make(chan LibreJSMetaInfo) // Make a channel that takes LibreJSMetaInfo
			linesParsed := 0                                    // Define linesParsed as the number of lines parsed by FileLicenseLineParser

			for _, lineContent := range fileContentLines { // For each license
				go FileLicenseLineParser(fileLineParserChannel, lineContent) // Asynchronously call FileLicenseLineParser
			}

		LineParserLoop:
			for libreJsMetaInfo := range fileLineParserChannel { // Constantly listen for channel input
				var endChannelListening bool

				linesParsed++ // Add one two linesParsed

				if libreJsMetaInfo.License != "" { // If the provided LibreJSMetaInfo has a valid License
					metaInfo = libreJsMetaInfo // Assign metaInfo as provided libreJsMetaInfo
					endChannelListening = true
				}

				if (fileContentLinesCount == linesParsed) || (endChannelListening) { // If we have parsed all lines or found the header info
					close(fileLineParserChannel) // Close the channel
					break LineParserLoop         // Break the loop
				}
			}

			if metaInfo.License == "" { // If there is no License defined by the end of the file
				getError = errors.New("LibreJS short-form header does not exist in this file.")
			}
		} else { // If the length of the file is 1 line or none
			getError = errors.New("File is either empty or does not contain the necessary individual lines required by LibreJS short-form blocks.")
		}
	} else { // If the file does not exist
		getError = errors.New(file + " does not exist.")
	}

	return metaInfo, getError
}

// FileLicenseLineParser
// This function handles individual line parsing
func FileLicenseLineParser(returnContentChannel chan LibreJSMetaInfo, lineContent string) {
	metaInfo := LibreJSMetaInfo{}

	lineContent = strings.Replace(lineContent, "//", "", -1) // Replace any // with nothing
	lineContent = strings.Replace(lineContent, "*", "", -1)  // Replace any * (block quotes) with nothing
	lineContent = strings.TrimPrefix(lineContent, " ")       // Trim any prefixed whitespace

	if strings.HasPrefix(lineContent, "@license") { // If the line starts with @license
		licenseHeaderFragments := strings.SplitN(lineContent, " ", 3)  // Split the license header info into three segments, separated by whitespace
		metaInfo.License = ParseLicenseName(licenseHeaderFragments[2]) // Define License as the parsed license name of the last item in fragments index
		metaInfo.Magnet = licenseHeaderFragments[1]                    // Define Magnet as the second item in the fragments index
	}

	returnContentChannel <- metaInfo
}

// GetMagnetLink
// This function will get a magnet link of the associated license exists
// Returns string for magnet link, error if item does not exist
func GetMagnetLink(license string) (string, error) {
	var magnetLinkFetchError error

	license = ParseLicenseName(license) // Parse the license name first
	magnetURL, licenseExists := LicenseMap[license]

	if !licenseExists { // If the license does not exist
		magnetLinkFetchError = errors.New(license + " does not exist.")
	}

	return magnetURL, magnetLinkFetchError
}

// ParseLicenseName
// This function will attempt to parse the provided license into a more logic naming scheme used in LicenseMap
func ParseLicenseName(license string) string {
	license = strings.ToLower(license) // Lowercase the entire string to make selective capitalization easier

	for _, licenseCapitalizedString := range LicensesCapitalizedStrings { // For each capitalized string of a license in LicensesCapitalizedStrings
		license = strings.Replace(license, strings.ToLower(licenseCapitalizedString), licenseCapitalizedString, -1) // Replace any lowercase instance with capitalized instance
	}

	license = strings.ToTitle(license)               // Title the license (example: apache -> Apache)
	license = strings.Replace(license, " ", "-", -1) // Replace whitespacing with hyphens

	return license
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/GaryBoone/GoStats/stats"
	"github.com/topher200/baseutil"
)

type criterionCalculationFunction func(teams []Team) Score
type PlayerFilter func(player Player) bool
type criterion struct {
	name      string                       // human readable name
	calculate criterionCalculationFunction // how to calculate the raw score
	filter    PlayerFilter                 // cull down to players that match
	weight    int                          // how much weight to give this score
	// worstCase is calculated at runtime to be the absolute worst score we can
	// see this criterion getting
	worstCase Score
}

var criteriaToScore = [...]criterion{
	criterion{"number of players", playerCountDifference, nil, 10, 0},
	criterion{"number of males", playerCountDifference, IsMale, 9, 0},
	criterion{"number of females", playerCountDifference, IsFemale, 9, 0},
	criterion{"average rating", ratingDifference, nil, 8, 0},
	criterion{"std dev of team ratings", ratingStdDev, nil, 5, 0},
}

func playerCountDifference(teams []Team) Score {
	teamLengths := make([]int, numTeams)
	for i, team := range teams {
		teamLengths[i] = len(team.players)
	}
	return Score(baseutil.StandardDeviationInt(teamLengths))
}

func ratingDifference(teams []Team) Score {
	teamAverageRatings := make([]float64, numTeams)
	for i, team := range teams {
		teamAverageRatings[i] = float64(AverageRating(team))
	}
	return Score(stats.StatsSampleStandardDeviation(teamAverageRatings))
}

func ratingStdDev(teams []Team) Score {
	teamRatingsStdDev := make([]float64, numTeams)
	for i, team := range teams {
		playerRatings := make([]float64, len(team.players))
		for j, player := range team.players {
			playerRatings[j] = float64(player.rating)
		}
		teamRatingsStdDev[i] = stats.StatsSampleStandardDeviation(playerRatings)
	}
	return Score(stats.StatsSampleStandardDeviation(teamRatingsStdDev))
}

func AverageRating(team Team) Score {
	sum := float32(0.0)
	for _, player := range team.players {
		sum += player.rating
	}
	return Score(sum / float32(len(team.players)))
}

func Filter(players []Player, filter PlayerFilter) (filteredPlayers []Player) {
	for _, player := range players {
		if filter == nil || filter(player) {
			filteredPlayers = append(filteredPlayers, player)
		}
	}
	return
}

// runCriterion by filtering the input teams and running the criterion function
func runCriterion(
	c criterion, teams []Team) (rawScore Score, weightedScore Score) {
	filteredTeams := make([]Team, len(teams))
	for i, _ := range teams {
		filteredTeams[i].players = Filter(teams[i].players, c.filter)
	}

	rawScore = c.calculate(filteredTeams)
	// We normalize our weighted scores based on the worst case scenario
	weightedScore = (rawScore / c.worstCase) * Score(c.weight)
	return rawScore, weightedScore
}

func maxScore(a, b Score) Score {
	if a > b {
		return a
	} else {
		return b
	}
}

// PopulateWorstCases calculates the worst case of each criterion.
//
// The function has the side effect of filling in the worstCase param for each
// criterion in criteriaToScore.
func PopulateWorstCases(solutions []Solution) {
	for _, solution := range solutions {
		_, rawScores := ScoreSolution(solution.players)
		for i, criterion := range criteriaToScore {
			criteriaToScore[i].worstCase = maxScore(
				criterion.worstCase, rawScores[i])
		}
	}
}

// Score a solution based on all known criteria.
//
// Returns the total score for the solution, as well as the raw score found for
// each of the criteriaToScore.
func ScoreSolution(players []Player) (totalScore Score, rawScores []Score) {
	teams := splitIntoTeams(players)
	rawScores = make([]Score, len(criteriaToScore))
	for i, criterion := range criteriaToScore {
		rawScore, weightedScore := runCriterion(criterion, teams)
		rawScores[i] = rawScore
		totalScore += weightedScore
	}
	return totalScore, rawScores
}

func PrintSolutionScoring(solution Solution) {
	teams := splitIntoTeams(solution.players)
	totalScore := Score(0)
	for _, criterion := range criteriaToScore {
		rawScore, weightedScore := runCriterion(criterion, teams)
		totalScore += weightedScore
		newLog.Info(
			"Balancing %s. Weighted score %.02f. Raw score %.02f (worst case %.02f). Running total: %.02f",
			criterion.name, weightedScore, rawScore, criterion.worstCase, totalScore)
	}
}
<commit_msg>tabulate score output<commit_after>package main

import (
	"fmt"
	"os"
	"text/tabwriter"

	"github.com/GaryBoone/GoStats/stats"
	"github.com/topher200/baseutil"
)

type criterionCalculationFunction func(teams []Team) Score
type PlayerFilter func(player Player) bool
type criterion struct {
	name      string                       // human readable name
	calculate criterionCalculationFunction // how to calculate the raw score
	filter    PlayerFilter                 // cull down to players that match
	weight    int                          // how much weight to give this score
	// worstCase is calculated at runtime to be the absolute worst score we can
	// see this criterion getting
	worstCase Score
}

var criteriaToScore = [...]criterion{
	criterion{"number of players", playerCountDifference, nil, 10, 0},
	criterion{"number of males", playerCountDifference, IsMale, 9, 0},
	criterion{"number of females", playerCountDifference, IsFemale, 9, 0},
	criterion{"average rating", ratingDifference, nil, 8, 0},
	criterion{"std dev of team ratings", ratingStdDev, nil, 5, 0},
}

func playerCountDifference(teams []Team) Score {
	teamLengths := make([]int, numTeams)
	for i, team := range teams {
		teamLengths[i] = len(team.players)
	}
	return Score(baseutil.StandardDeviationInt(teamLengths))
}

func ratingDifference(teams []Team) Score {
	teamAverageRatings := make([]float64, numTeams)
	for i, team := range teams {
		teamAverageRatings[i] = float64(AverageRating(team))
	}
	return Score(stats.StatsSampleStandardDeviation(teamAverageRatings))
}

func ratingStdDev(teams []Team) Score {
	teamRatingsStdDev := make([]float64, numTeams)
	for i, team := range teams {
		playerRatings := make([]float64, len(team.players))
		for j, player := range team.players {
			playerRatings[j] = float64(player.rating)
		}
		teamRatingsStdDev[i] = stats.StatsSampleStandardDeviation(playerRatings)
	}
	return Score(stats.StatsSampleStandardDeviation(teamRatingsStdDev))
}

func AverageRating(team Team) Score {
	sum := float32(0.0)
	for _, player := range team.players {
		sum += player.rating
	}
	return Score(sum / float32(len(team.players)))
}

func Filter(players []Player, filter PlayerFilter) (filteredPlayers []Player) {
	for _, player := range players {
		if filter == nil || filter(player) {
			filteredPlayers = append(filteredPlayers, player)
		}
	}
	return
}

// runCriterion by filtering the input teams and running the criterion function
func runCriterion(
	c criterion, teams []Team) (rawScore Score, weightedScore Score) {
	filteredTeams := make([]Team, len(teams))
	for i, _ := range teams {
		filteredTeams[i].players = Filter(teams[i].players, c.filter)
	}

	rawScore = c.calculate(filteredTeams)
	// We normalize our weighted scores based on the worst case scenario
	weightedScore = (rawScore / c.worstCase) * Score(c.weight)
	return rawScore, weightedScore
}

func maxScore(a, b Score) Score {
	if a > b {
		return a
	} else {
		return b
	}
}

// PopulateWorstCases calculates the worst case of each criterion.
//
// The function has the side effect of filling in the worstCase param for each
// criterion in criteriaToScore.
func PopulateWorstCases(solutions []Solution) {
	for _, solution := range solutions {
		_, rawScores := ScoreSolution(solution.players)
		for i, criterion := range criteriaToScore {
			criteriaToScore[i].worstCase = maxScore(
				criterion.worstCase, rawScores[i])
		}
	}
}

// Score a solution based on all known criteria.
//
// Returns the total score for the solution, as well as the raw score found for
// each of the criteriaToScore.
func ScoreSolution(players []Player) (totalScore Score, rawScores []Score) {
	teams := splitIntoTeams(players)
	rawScores = make([]Score, len(criteriaToScore))
	for i, criterion := range criteriaToScore {
		rawScore, weightedScore := runCriterion(criterion, teams)
		rawScores[i] = rawScore
		totalScore += weightedScore
	}
	return totalScore, rawScores
}

func PrintSolutionScoring(solution Solution) {
	teams := splitIntoTeams(solution.players)
	totalScore := Score(0)
	writer := new(tabwriter.Writer)
	writer.Init(os.Stdout, 0, 0, 1, ' ', 0)
	for _, criterion := range criteriaToScore {
		rawScore, weightedScore := runCriterion(criterion, teams)
		totalScore += weightedScore
		fmt.Fprintf(
			writer,
			"Balancing %s.\tWeighted score %.02f.\tRaw score %.02f (worst case %.02f).\tRunning total: %.02f\n",
			criterion.name, weightedScore, rawScore, criterion.worstCase, totalScore)
	}
	writer.Flush()
}
<|endoftext|>


<commit_before>// +build linux

/*
Copyright 2016 The Kubernetes Authors.

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 volume

import (
	"path/filepath"
	"syscall"

	"k8s.io/kubernetes/pkg/util/chmod"
	"k8s.io/kubernetes/pkg/util/chown"

	"os"

	"github.com/golang/glog"
)

const (
	rwMask = os.FileMode(0660)
	roMask = os.FileMode(0440)
)

// SetVolumeOwnership modifies the given volume to be owned by
// fsGroup, and sets SetGid so that newly created files are owned by
// fsGroup. If fsGroup is nil nothing is done.
func SetVolumeOwnership(mounter Mounter, fsGroup *int64) error {

	if fsGroup == nil {
		return nil
	}

	chownRunner := chown.New()
	chmodRunner := chmod.New()
	return filepath.Walk(mounter.GetPath(), func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		stat, ok := info.Sys().(*syscall.Stat_t)
		if !ok {
			return nil
		}

		if stat == nil {
			glog.Errorf("Got nil stat_t for path %v while setting ownership of volume", path)
			return nil
		}

		err = chownRunner.Chown(path, int(stat.Uid), int(*fsGroup))
		if err != nil {
			glog.Errorf("Chown failed on %v: %v", path, err)
		}

		mask := rwMask
		if mounter.GetAttributes().ReadOnly {
			mask = roMask
		}

		err = chmodRunner.Chmod(path, info.Mode()|mask|os.ModeSetgid)
		if err != nil {
			glog.Errorf("Chmod failed on %v: %v", path, err)
		}

		return nil
	})
}
<commit_msg>UPSTREAM: 36386: Avoid setting S_ISGID on files in volumes<commit_after>// +build linux

/*
Copyright 2016 The Kubernetes Authors.

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 volume

import (
	"path/filepath"
	"syscall"

	"k8s.io/kubernetes/pkg/util/chmod"
	"k8s.io/kubernetes/pkg/util/chown"

	"os"

	"github.com/golang/glog"
)

const (
	rwMask = os.FileMode(0660)
	roMask = os.FileMode(0440)
)

// SetVolumeOwnership modifies the given volume to be owned by
// fsGroup, and sets SetGid so that newly created files are owned by
// fsGroup. If fsGroup is nil nothing is done.
func SetVolumeOwnership(mounter Mounter, fsGroup *int64) error {

	if fsGroup == nil {
		return nil
	}

	chownRunner := chown.New()
	chmodRunner := chmod.New()
	return filepath.Walk(mounter.GetPath(), func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		stat, ok := info.Sys().(*syscall.Stat_t)
		if !ok {
			return nil
		}

		if stat == nil {
			glog.Errorf("Got nil stat_t for path %v while setting ownership of volume", path)
			return nil
		}

		err = chownRunner.Chown(path, int(stat.Uid), int(*fsGroup))
		if err != nil {
			glog.Errorf("Chown failed on %v: %v", path, err)
		}

		mask := rwMask
		if mounter.GetAttributes().ReadOnly {
			mask = roMask
		}

		if info.IsDir() {
			mask |= os.ModeSetgid
		}

		err = chmodRunner.Chmod(path, info.Mode()|mask)
		if err != nil {
			glog.Errorf("Chmod failed on %v: %v", path, err)
		}

		return nil
	})
}
<|endoftext|>


<commit_before>package bugsnag

import (
	"log"
	"os"
	"testing"

	"github.com/juju/loggo"
)

func TestNotifyReleaseStages(t *testing.T) {

	var testCases = []struct {
		stage      string
		configured []string
		notify     bool
		msg        string
	}{
		{
			stage:  "production",
			notify: true,
			msg:    "Should notify in all release stages by default",
		},
		{
			stage:      "production",
			configured: []string{"development", "production"},
			notify:     true,
			msg:        "Failed to notify in configured release stage",
		},
		{
			stage:      "staging",
			configured: []string{"development", "production"},
			notify:     false,
			msg:        "Failed to prevent notification in excluded release stage",
		},
	}

	for _, testCase := range testCases {
		Configure(Configuration{ReleaseStage: testCase.stage, NotifyReleaseStages: testCase.configured})

		if Config.notifyInReleaseStage() != testCase.notify {
			t.Error(testCase.msg)
		}
	}
}

func TestIsProjectPackage(t *testing.T) {

	Configure(Configuration{ProjectPackages: []string{
		"main",
		"star*",
		"example.com/a",
		"example.com/b/*",
		"example.com/c/*/*",
		"example.com/d/**",
		"example.com/e",
	}})

	var testCases = []struct {
		Path     string
		Included bool
	}{
		{"", false},
		{"main", true},
		{"runtime", false},

		{"star", true},
		{"sta", false},
		{"starred", true},
		{"star/foo", false},

		{"example.com/a", true},

		{"example.com/b", false},
		{"example.com/b/", true},
		{"example.com/b/foo", true},
		{"example.com/b/foo/bar", false},

		{"example.com/c/foo/bar", true},
		{"example.com/c/foo/bar/baz", false},

		{"example.com/d/foo/bar", true},
		{"example.com/d/foo/bar/baz", true},

		{"example.com/e", true},
	}

	for _, s := range testCases {
		if Config.isProjectPackage(s.Path) != s.Included {
			t.Error("literal project package doesn't work:", s.Path, s.Included)
		}
	}
}

func TestStripProjectPackage(t *testing.T) {

	Configure(Configuration{ProjectPackages: []string{
		"main",
		"star*",
		"example.com/a",
		"example.com/b/*",
		"example.com/c/**",
	}})

	gopath := os.Getenv("GOPATH")
	var testCases = []struct {
		File     string
		Stripped string
	}{
		{"main.go", "main.go"},
		{"runtime.go", "runtime.go"},
		{"star.go", "star.go"},

		{"example.com/a/foo.go", "foo.go"},

		{"example.com/b/foo/bar.go", "foo/bar.go"},
		{"example.com/b/foo.go", "foo.go"},

		{"example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},

		{"example.com/c/a/b/foo.go", "a/b/foo.go"},

		{gopath + "/src/runtime.go", "runtime.go"},
		{gopath + "/src/example.com/a/foo.go", "foo.go"},
		{gopath + "/src/example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},
		{gopath + "/src/example.com/c/a/b/foo.go", "a/b/foo.go"},
	}

	for _, tc := range testCases {
		if s := Config.stripProjectPackages(tc.File); s != tc.Stripped {
			t.Error("stripProjectPackage did not remove expected path:", tc.File, tc.Stripped, "was:", s)
		}
	}
}

func TestStripCustomSourceRoot(t *testing.T) {
	Configure(Configuration{
		ProjectPackages: []string{
			"main",
			"star*",
			"example.com/a",
			"example.com/b/*",
			"example.com/c/**",
		},
		SourceRoot: "/Users/bob/code/go/src/",
	})
	var testCases = []struct {
		File     string
		Stripped string
	}{
		{"main.go", "main.go"},
		{"runtime.go", "runtime.go"},
		{"star.go", "star.go"},

		{"example.com/a/foo.go", "foo.go"},

		{"example.com/b/foo/bar.go", "foo/bar.go"},
		{"example.com/b/foo.go", "foo.go"},

		{"example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},

		{"example.com/c/a/b/foo.go", "a/b/foo.go"},

		{"/Users/bob/code/go/src/runtime.go", "runtime.go"},
		{"/Users/bob/code/go/src/example.com/a/foo.go", "foo.go"},
		{"/Users/bob/code/go/src/example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},
		{"/Users/bob/code/go/src/example.com/c/a/b/foo.go", "a/b/foo.go"},
	}

	for _, tc := range testCases {
		if s := Config.stripProjectPackages(tc.File); s != tc.Stripped {
			t.Error("stripProjectPackage did not remove expected path:", tc.File, tc.Stripped, "was:", s)
		}
	}
}

type LoggoWrapper struct {
	loggo.Logger
}

func (lw *LoggoWrapper) Printf(format string, v ...interface{}) {
	lw.Logger.Warningf(format, v...)
}

func TestConfiguringCustomLogger(t *testing.T) {

	l1 := log.New(os.Stdout, "", log.Lshortfile)

	l2 := &LoggoWrapper{loggo.GetLogger("test")}

	var testCases = []struct {
		config Configuration
		notify bool
		msg    string
	}{
		{
			config: Configuration{ReleaseStage: "production", NotifyReleaseStages: []string{"development", "production"}, Logger: l1},
			notify: true,
			msg:    "Failed to assign log.Logger",
		},
		{
			config: Configuration{ReleaseStage: "production", NotifyReleaseStages: []string{"development", "production"}, Logger: l2},
			notify: true,
			msg:    "Failed to assign LoggoWrapper",
		},
	}

	for _, testCase := range testCases {
		Configure(testCase.config)

		// call printf just to illustrate it is present as the compiler does most of the hard work
		testCase.config.Logger.Printf("hello %s", "bugsnag")

	}
}
<commit_msg>tests: Remove dependency on loggo<commit_after>package bugsnag

import (
	"log"
	"os"
	"testing"
)

func TestNotifyReleaseStages(t *testing.T) {

	var testCases = []struct {
		stage      string
		configured []string
		notify     bool
		msg        string
	}{
		{
			stage:  "production",
			notify: true,
			msg:    "Should notify in all release stages by default",
		},
		{
			stage:      "production",
			configured: []string{"development", "production"},
			notify:     true,
			msg:        "Failed to notify in configured release stage",
		},
		{
			stage:      "staging",
			configured: []string{"development", "production"},
			notify:     false,
			msg:        "Failed to prevent notification in excluded release stage",
		},
	}

	for _, testCase := range testCases {
		Configure(Configuration{ReleaseStage: testCase.stage, NotifyReleaseStages: testCase.configured})

		if Config.notifyInReleaseStage() != testCase.notify {
			t.Error(testCase.msg)
		}
	}
}

func TestIsProjectPackage(t *testing.T) {

	Configure(Configuration{ProjectPackages: []string{
		"main",
		"star*",
		"example.com/a",
		"example.com/b/*",
		"example.com/c/*/*",
		"example.com/d/**",
		"example.com/e",
	}})

	var testCases = []struct {
		Path     string
		Included bool
	}{
		{"", false},
		{"main", true},
		{"runtime", false},

		{"star", true},
		{"sta", false},
		{"starred", true},
		{"star/foo", false},

		{"example.com/a", true},

		{"example.com/b", false},
		{"example.com/b/", true},
		{"example.com/b/foo", true},
		{"example.com/b/foo/bar", false},

		{"example.com/c/foo/bar", true},
		{"example.com/c/foo/bar/baz", false},

		{"example.com/d/foo/bar", true},
		{"example.com/d/foo/bar/baz", true},

		{"example.com/e", true},
	}

	for _, s := range testCases {
		if Config.isProjectPackage(s.Path) != s.Included {
			t.Error("literal project package doesn't work:", s.Path, s.Included)
		}
	}
}

func TestStripProjectPackage(t *testing.T) {

	Configure(Configuration{ProjectPackages: []string{
		"main",
		"star*",
		"example.com/a",
		"example.com/b/*",
		"example.com/c/**",
	}})

	gopath := os.Getenv("GOPATH")
	var testCases = []struct {
		File     string
		Stripped string
	}{
		{"main.go", "main.go"},
		{"runtime.go", "runtime.go"},
		{"star.go", "star.go"},

		{"example.com/a/foo.go", "foo.go"},

		{"example.com/b/foo/bar.go", "foo/bar.go"},
		{"example.com/b/foo.go", "foo.go"},

		{"example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},

		{"example.com/c/a/b/foo.go", "a/b/foo.go"},

		{gopath + "/src/runtime.go", "runtime.go"},
		{gopath + "/src/example.com/a/foo.go", "foo.go"},
		{gopath + "/src/example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},
		{gopath + "/src/example.com/c/a/b/foo.go", "a/b/foo.go"},
	}

	for _, tc := range testCases {
		if s := Config.stripProjectPackages(tc.File); s != tc.Stripped {
			t.Error("stripProjectPackage did not remove expected path:", tc.File, tc.Stripped, "was:", s)
		}
	}
}

func TestStripCustomSourceRoot(t *testing.T) {
	Configure(Configuration{
		ProjectPackages: []string{
			"main",
			"star*",
			"example.com/a",
			"example.com/b/*",
			"example.com/c/**",
		},
		SourceRoot: "/Users/bob/code/go/src/",
	})
	var testCases = []struct {
		File     string
		Stripped string
	}{
		{"main.go", "main.go"},
		{"runtime.go", "runtime.go"},
		{"star.go", "star.go"},

		{"example.com/a/foo.go", "foo.go"},

		{"example.com/b/foo/bar.go", "foo/bar.go"},
		{"example.com/b/foo.go", "foo.go"},

		{"example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},

		{"example.com/c/a/b/foo.go", "a/b/foo.go"},

		{"/Users/bob/code/go/src/runtime.go", "runtime.go"},
		{"/Users/bob/code/go/src/example.com/a/foo.go", "foo.go"},
		{"/Users/bob/code/go/src/example.com/x/a/b/foo.go", "example.com/x/a/b/foo.go"},
		{"/Users/bob/code/go/src/example.com/c/a/b/foo.go", "a/b/foo.go"},
	}

	for _, tc := range testCases {
		if s := Config.stripProjectPackages(tc.File); s != tc.Stripped {
			t.Error("stripProjectPackage did not remove expected path:", tc.File, tc.Stripped, "was:", s)
		}
	}
}

type CustomTestLogger struct {
}

func (logger *CustomTestLogger) Printf(format string, v ...interface{}) {
}

func TestConfiguringCustomLogger(t *testing.T) {

	l1 := log.New(os.Stdout, "", log.Lshortfile)

	l2 := &CustomTestLogger{}

	var testCases = []struct {
		config Configuration
		notify bool
		msg    string
	}{
		{
			config: Configuration{ReleaseStage: "production", NotifyReleaseStages: []string{"development", "production"}, Logger: l1},
		},
		{
			config: Configuration{ReleaseStage: "production", NotifyReleaseStages: []string{"development", "production"}, Logger: l2},
		},
	}

	for _, testCase := range testCases {
		Configure(testCase.config)

		// call printf just to illustrate it is present as the compiler does most of the hard work
		testCase.config.Logger.Printf("hello %s", "bugsnag")

	}
}
<|endoftext|>


<commit_before>package qtypes

import (
	"context"
	"fmt"
	"errors"
	"github.com/docker/docker/client"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/events"
)

const (
	mobyAPI = "v1.29"
)

type ContainerInventory struct {
	Client *client.Client
	Data   map[string]types.ContainerJSON
	IDtoIP map[string]string
}

func NewContainerInventory(cli *client.Client) ContainerInventory {
	return ContainerInventory{
		Client: cli,
		Data: map[string]types.ContainerJSON{},
		IDtoIP: map[string]string{},
	}
}

func NewPlainContainerInventory() ContainerInventory {
	return ContainerInventory{
		Data: map[string]types.ContainerJSON{},
	}
}

func NewContainerInventoryHost(dockerHost string) ContainerInventory {
	engineCli, _ := client.NewClient(dockerHost, mobyAPI, nil, nil)
	return ContainerInventory{
		Client: engineCli,
		Data: map[string]types.ContainerJSON{},
		IDtoIP: map[string]string{},
	}
}

func (ci *ContainerInventory) SetCnt(key string, cnt types.ContainerJSON) (err error) {
	ci.Data[key] = cnt
	return
}

func (ci *ContainerInventory) GetCnt(key string) (cnt types.ContainerJSON, err error) {
	if cnt, ok := ci.Data[key];ok {
		return cnt, err
	}
	err = errors.New(fmt.Sprintf("No container found with key '%s'", key))
	return
}

func (ci *ContainerInventory) GetCntByID(id string) (cnt types.ContainerJSON, err error) {
	if cnt, ok := ci.Data[id];ok {
		return cnt, err
	}
	return cnt, err
}

func (ci *ContainerInventory) GetCntByIP(ip string) (cnt types.ContainerJSON, err error) {
	for id, v := range ci.IDtoIP {
		if ip == v {
			return ci.GetCntByID(id)
		}

	}
	return cnt, err
}

func (ci *ContainerInventory) SetCntByEvent(event events.Message) (err error) {
	id := event.Actor.ID
	if event.Type != "container" {
		return
	}
	switch event.Action {
	case "die", "destroy":
		if _, ok := ci.IDtoIP[id]; ok {
			delete(ci.IDtoIP, id)
		}
		if _, ok := ci.Data[id]; ok {
			delete(ci.Data, id)
		}
		return
	case "start":
		cnt, err := ci.Client.ContainerInspect(context.Background(), id)
		if err != nil {
			return err
		}
		ci.Data[id] = cnt
		if cnt.State.Running {
			for _, v := range cnt.NetworkSettings.Networks {
				ci.IDtoIP[id] = v.IPAddress
			}
		} else {
			fmt.Printf("cnt.State: %v\n", cnt.State)
		}
	}
	return err
}

func (ci *ContainerInventory) GetCntByEvent(event events.Message) (cnt types.ContainerJSON, err error) {
	id := event.Actor.ID
	if event.Type != "container" {
		return
	}
	switch event.Action {
	case "die", "destroy":
		if _, ok := ci.IDtoIP[id]; ok {
			delete(ci.IDtoIP, id)
		}
		if _, ok := ci.Data[id]; ok {
			delete(ci.Data, id)
		}
		return
	case "start":
		cnt, err = ci.Client.ContainerInspect(context.Background(), id)
		ci.Data[id] = cnt
		if cnt.State.Running {
			for _, v := range cnt.NetworkSettings.Networks {
				ci.IDtoIP[id] = v.IPAddress
			}
		} else {
			fmt.Printf("cnt.State: %v\n", cnt.State)
		}
	default:
		cnt, err = ci.Client.ContainerInspect(context.Background(), id)
	}
	return cnt, err
}<commit_msg>ContainerInventory does no reach out to mobyEngine<commit_after>package qtypes

import (
	"fmt"
	"errors"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/events"
)

type ContainerInventory struct {
	Data   map[string]types.ContainerJSON
	IDtoIP map[string]string
}

func NewContainerInventory() ContainerInventory {
	return ContainerInventory{
		Data: map[string]types.ContainerJSON{},
		IDtoIP: map[string]string{},
	}
}

func NewPlainContainerInventory() ContainerInventory {
	return ContainerInventory{
		Data: map[string]types.ContainerJSON{},
	}
}

func (ci *ContainerInventory) SetCnt(key string, cnt types.ContainerJSON) (err error) {
	ci.Data[key] = cnt
	return
}

func (ci *ContainerInventory) GetCnt(key string) (cnt types.ContainerJSON, err error) {
	if cnt, ok := ci.Data[key];ok {
		return cnt, err
	}
	err = errors.New(fmt.Sprintf("No container found with key '%s'", key))
	return
}

func (ci *ContainerInventory) GetCntByID(id string) (cnt types.ContainerJSON, err error) {
	if cnt, ok := ci.Data[id];ok {
		return cnt, err
	}
	return cnt, err
}

func (ci *ContainerInventory) GetCntByIP(ip string) (cnt types.ContainerJSON, err error) {
	for id, v := range ci.IDtoIP {
		if ip == v {
			return ci.GetCntByID(id)
		}

	}
	return cnt, err
}

func (ci *ContainerInventory) SetCntByEvent(ce ContainerEvent) (err error) {
	id := ce.Event.Actor.ID
	event := ce.Event
	if event.Type != "container" {
		return
	}
	switch event.Action {
	case "die", "destroy":
		if _, ok := ci.IDtoIP[id]; ok {
			delete(ci.IDtoIP, id)
		}
		if _, ok := ci.Data[id]; ok {
			delete(ci.Data, id)
		}
		return
	case "start":
		cnt := ce.Container
		if err != nil {
			return err
		}
		ci.Data[id] = cnt
		if cnt.State.Running {
			for _, v := range cnt.NetworkSettings.Networks {
				ci.IDtoIP[id] = v.IPAddress
			}
		} else {
			fmt.Printf("cnt.State: %v\n", cnt.State)
		}
	}
	return err
}

func (ci *ContainerInventory) GetCntByEvent(event events.Message) (cnt types.ContainerJSON, err error) {
	fmt.Println("Sorry, have to redo GetCntByEvent...")
	return
	id := event.Actor.ID
	if event.Type != "container" {
		return
	}
	switch event.Action {
	case "die", "destroy":
		if _, ok := ci.IDtoIP[id]; ok {
			delete(ci.IDtoIP, id)
		}
		if _, ok := ci.Data[id]; ok {
			delete(ci.Data, id)
		}
		return
	case "start":
		ci.Data[id] = cnt
		if cnt.State.Running {
			for _, v := range cnt.NetworkSettings.Networks {
				ci.IDtoIP[id] = v.IPAddress
			}
		} else {
			fmt.Printf("cnt.State: %v\n", cnt.State)
		}
	}
	return cnt, err
}<|endoftext|>


<commit_before>// Copyright (c) 2016 Niklas Wolber
// This file is licensed under the MIT license.
// See the LICENSE file for more information.

package job

import (
	"context"
	"fmt"
	"io"
	"log"
	"net"
	"os"

	"golang.org/x/crypto/ssh"
)

// forwardRemote instructs the connect SSH server to forward all connections attempts
// on remoteAddr to the local client. The client will then establish a connection
// to localAddr and forward any payload exchanged.
//
// Allocated resources will be released, when the context completes.
func forwardRemote(ctx context.Context, client *ssh.Client, remoteAddr string, localAddr string) {
	l, ok := ctx.Value(loggerKey).(Logger)
	if !ok || l == nil {
		l = log.New(os.Stderr, "", log.LstdFlags)
	}

	listener, err := client.Listen("tcp", remoteAddr)
	if err != nil {
		err = fmt.Errorf("unable to listen to %s on remote host %s: %s", remoteAddr, client.RemoteAddr(), err)
		l.Println(err)
		return
	}

	go runTunnel(ctx, listener, net.Dial, localAddr)
}

// forwardLocal forwards all connection attempts on localAddr to the remote host client
// connects to. The remote host will then establish a connection remoteAddr.
//
// Allocated resources will be released, when the context completes.
func forwardLocal(ctx context.Context, client *ssh.Client, remoteAddr string, localAddr string) {
	l, ok := ctx.Value(loggerKey).(Logger)
	if !ok || l == nil {
		l = log.New(os.Stderr, "", log.LstdFlags)
	}

	listener, err := net.Listen("tcp", localAddr)
	if err != nil {
		err = fmt.Errorf("unable to listen to %s: %s", localAddr, err)
		l.Println(err)
		return
	}

	go runTunnel(ctx, listener, client.Dial, remoteAddr)
}

type dial func(network, address string) (net.Conn, error)

func runTunnel(ctx context.Context, listener net.Listener, d dial, addr string) {
	l, ok := ctx.Value(loggerKey).(Logger)
	if !ok || l == nil {
		l = log.New(os.Stderr, "", log.LstdFlags)
	}

	acceptChan := accept(ctx, listener)

	for {
		select {
		case remoteConn, ok := <-acceptChan:
			if !ok {
				l.Println("accept channel closed")
				return
			}

			if remoteConn.error != nil {
				l.Println("error accepting tunnel connection", remoteConn.error)
				return
			}

			go func(conn net.Conn) {
				defer conn.Close()
				identity := fmt.Sprintf("%s->%s", conn.RemoteAddr(), conn.LocalAddr())

				l.Println("accepted tunnel connection", identity)

				localConn, err := d("tcp", addr)
				if err != nil {
					l.Println(identity, "unable to connect to endpoint", addr, err)
					return
				}
				l.Println(identity, "connected to endpoint")

				go copyConn(identity, localConn, conn)
				copyConn(identity, conn, localConn)
				l.Println(identity, "tunnel connection closed")
			}(remoteConn)

		case <-ctx.Done():
			l.Println("context done, closing tunnel on", listener.Addr())
			return
		}
	}
}

func copyConn(identity string, writer io.Writer, reader io.Reader) {
	_, err := io.Copy(writer, reader)
	if err != nil {
		log.Println(identity, "io.Copy error:", err)
	}
}
<commit_msg>Further improved tunnel logging<commit_after>// Copyright (c) 2016 Niklas Wolber
// This file is licensed under the MIT license.
// See the LICENSE file for more information.

package job

import (
	"context"
	"fmt"
	"io"
	"log"
	"net"
	"os"

	"golang.org/x/crypto/ssh"
)

// forwardRemote instructs the connect SSH server to forward all connections attempts
// on remoteAddr to the local client. The client will then establish a connection
// to localAddr and forward any payload exchanged.
//
// Allocated resources will be released, when the context completes.
func forwardRemote(ctx context.Context, client *ssh.Client, remoteAddr string, localAddr string) {
	l, ok := ctx.Value(loggerKey).(Logger)
	if !ok || l == nil {
		l = log.New(os.Stderr, "", log.LstdFlags)
	}

	listener, err := client.Listen("tcp", remoteAddr)
	if err != nil {
		err = fmt.Errorf("unable to listen to %s on remote host %s: %s", remoteAddr, client.RemoteAddr(), err)
		l.Println(err)
		return
	}

	go runTunnel(ctx, listener, net.Dial, localAddr)
}

// forwardLocal forwards all connection attempts on localAddr to the remote host client
// connects to. The remote host will then establish a connection remoteAddr.
//
// Allocated resources will be released, when the context completes.
func forwardLocal(ctx context.Context, client *ssh.Client, remoteAddr string, localAddr string) {
	l, ok := ctx.Value(loggerKey).(Logger)
	if !ok || l == nil {
		l = log.New(os.Stderr, "", log.LstdFlags)
	}

	listener, err := net.Listen("tcp", localAddr)
	if err != nil {
		err = fmt.Errorf("unable to listen to %s: %s", localAddr, err)
		l.Println(err)
		return
	}

	go runTunnel(ctx, listener, client.Dial, remoteAddr)
}

type dial func(network, address string) (net.Conn, error)

func runTunnel(ctx context.Context, listener net.Listener, d dial, addr string) {
	l, ok := ctx.Value(loggerKey).(Logger)
	if !ok || l == nil {
		l = log.New(os.Stderr, "", log.LstdFlags)
	}

	acceptChan := accept(ctx, listener)

	for {
		select {
		case remoteConn, ok := <-acceptChan:
			if !ok {
				l.Println("accept channel closed")
				return
			}

			if remoteConn.error != nil {
				l.Println("error accepting tunnel connection", remoteConn.error)
				return
			}

			go func(conn net.Conn) {
				defer conn.Close()
				identity := fmt.Sprintf("%s->%s", conn.RemoteAddr(), conn.LocalAddr())

				l.Println("accepted tunnel connection", identity)

				localConn, err := d("tcp", addr)
				if err != nil {
					l.Println(identity, "unable to connect to endpoint", addr, err)
					return
				}
				l.Println(identity, "connected to endpoint", addr)

				go copyConn(identity, localConn, conn)
				copyConn(identity, conn, localConn)
				l.Println(identity, "tunnel connection closed to", addr)
			}(remoteConn)

		case <-ctx.Done():
			l.Println("context done, closing tunnel on", listener.Addr())
			return
		}
	}
}

func copyConn(identity string, writer io.Writer, reader io.Reader) {
	_, err := io.Copy(writer, reader)
	if err != nil {
		log.Println(identity, "io.Copy error:", err)
	}
}
<|endoftext|>


<commit_before>package gitfile

import (
	b64 "encoding/base64"
	"os/exec"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"github.com/hashicorp/terraform/helper/schema"
	"strings"
	"github.com/hashicorp/terraform/terraform"
)

func Provider() terraform.ResourceProvider {
	return &schema.Provider{
		Schema: map[string]*schema.Schema{
			"workdir": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
		},
		ResourcesMap: map[string]*schema.Resource{
			"gitfile_file": fileResource(),
		},
		ConfigureFunc: gitfileConfigure,
	}
}

func gitfileConfigure(data *schema.ResourceData) (interface{}, error) {
	config := &gitfileConfig {
		workDir: data.Get("workdir").(string),
	}
	return config, nil
}

type gitfileConfig struct {
	workDir string
}

func fileResource() *schema.Resource {
	return &schema.Resource {
		Schema: map[string]*schema.Schema {
			"repo": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"path": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"contents": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"branch": &schema.Schema{
				Type: schema.TypeString,
				Optional: true,
				Default: "master",
			},
		},
		Create: FileCreate,
		Read: FileRead,
		Update: FileUpdate,
		Delete: FileDelete,
	}
}

func FileCreate(d *schema.ResourceData, meta interface{}) error {
	d.SetId(fmt.Sprintf("%s %s %s", d.Get("repo"), d.Get("branch"), d.Get("path")))
	return nil
}
func FileRead(d *schema.ResourceData, meta interface{}) error {
	splits := strings.SplitN(d.Id(), " ", 3)
	repo := splits[0]
	branch := splits[1]
	filepath := splits[2]
	workdir := meta.(*gitfileConfig).workDir

	d.Set("repo", repo)
	d.Set("branch", branch)
	d.Set("path", filepath)

	checkout_dir := path.Join(workdir, mungeGitDir(d.Id()))
	if err := shallowSparseGitCheckout(checkout_dir, repo, branch, filepath); err != nil {
		return err
	}

	contents, err := ioutil.ReadFile(path.Join(checkout_dir, filepath))
	if err != nil {
		if os.IsNotExist(err) {
			d.Set("contents", "")
		} else {
			return err
		}
	} else {
		d.Set("contents", string(contents))
	}
	return nil
}
func FileUpdate(d *schema.ResourceData, meta interface{}) error {
	return nil
}
func FileDelete(d *schema.ResourceData, meta interface{}) error {
	return nil
}

func mungeGitDir(id string) string {
	return b64.URLEncoding.EncodeToString([]byte(id))
}

func shallowSparseGitCheckout(checkout_dir, repo, branch, filepath string) error {
	if err := os.MkdirAll(checkout_dir, 0755); err != nil {
		return err
	}

	// git init appears to be idempotent.
	git_init := exec.Command("git", "init")
	git_init.Dir = checkout_dir
	if err := git_init.Run(); err != nil {
		return err
	}

	git_config := exec.Command("git", "config", "core.sparsecheckout", "true")
	git_config.Dir = checkout_dir
	if err := git_config.Run(); err != nil {
		return err
	}

	scf, err := os.Create(path.Join(checkout_dir, ".git", "info", "sparse-checkout"), )
	if err != nil {
		return err
	}
	if _, err := scf.WriteString(filepath); err != nil {
		return err
	}
	if err := scf.Close(); err != nil {
		return err
	}

	git_fetch := exec.Command("git", "fetch", "--depth", "1", repo, branch)
	git_fetch.Dir = checkout_dir
	if err := git_fetch.Run(); err != nil {
		return err
	}

	git_checkout := exec.Command("git", "checkout", "--force", "FETCH_HEAD")
	git_checkout.Dir = checkout_dir
	if err := git_checkout.Run(); err != nil {
		return err
	}
	return nil
}<commit_msg>Create/Update semantics for gitfile_file<commit_after>package gitfile

import (
	b64 "encoding/base64"
	"os/exec"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"github.com/hashicorp/terraform/helper/schema"
	"strings"
	"github.com/hashicorp/terraform/terraform"
)

func Provider() terraform.ResourceProvider {
	return &schema.Provider{
		Schema: map[string]*schema.Schema{
			"workdir": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
		},
		ResourcesMap: map[string]*schema.Resource{
			"gitfile_file": fileResource(),
		},
		ConfigureFunc: gitfileConfigure,
	}
}

func gitfileConfigure(data *schema.ResourceData) (interface{}, error) {
	config := &gitfileConfig {
		workDir: data.Get("workdir").(string),
	}
	return config, nil
}

type gitfileConfig struct {
	workDir string
}

func fileResource() *schema.Resource {
	return &schema.Resource {
		Schema: map[string]*schema.Schema {
			"repo": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"path": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"contents": &schema.Schema{
				Type: schema.TypeString,
				Required: true,
			},
			"branch": &schema.Schema{
				Type: schema.TypeString,
				Optional: true,
				Default: "master",
			},
			"commit_message": &schema.Schema{
				Type: schema.TypeString,
				Optional: true,
				Default: "Created by terraform gitfile_file",
			},
		},
		Create: FileCreate,
		Read: FileRead,
		Update: FileUpdate,
		Delete: FileDelete,
	}
}

func FileCreate(d *schema.ResourceData, meta interface{}) error {
	repo := d.Get("repo").(string)
	branch := d.Get("branch").(string)
	filepath := d.Get("path").(string)
	workdir := meta.(*gitfileConfig).workDir

	d.SetId(fmt.Sprintf("%s %s %s", repo, branch, filepath))

	checkout_dir := path.Join(workdir, mungeGitDir(d.Id()))
	if err := shallowSparseGitCheckout(checkout_dir, repo, branch, filepath); err != nil {
		return err
	}

	return FileUpdate(d, meta)
}
func FileRead(d *schema.ResourceData, meta interface{}) error {
	splits := strings.SplitN(d.Id(), " ", 3)
	repo := splits[0]
	branch := splits[1]
	filepath := splits[2]
	workdir := meta.(*gitfileConfig).workDir

	d.Set("repo", repo)
	d.Set("branch", branch)
	d.Set("path", filepath)

	checkout_dir := path.Join(workdir, mungeGitDir(d.Id()))
	if err := shallowSparseGitCheckout(checkout_dir, repo, branch, filepath); err != nil {
		return err
	}

	contents, err := ioutil.ReadFile(path.Join(checkout_dir, filepath))
	if err != nil {
		if os.IsNotExist(err) {
			d.Set("contents", "")
		} else {
			return err
		}
	} else {
		d.Set("contents", string(contents))
	}
	return nil
}
func FileUpdate(d *schema.ResourceData, meta interface{}) error {
	repo := d.Get("repo").(string)
	branch := d.Get("branch").(string)
	filepath := d.Get("path").(string)
	contents := d.Get("contents").(string)
	commit_message := d.Get("commit_message").(string)
	workdir := meta.(*gitfileConfig).workDir
	checkout_dir := path.Join(workdir, mungeGitDir(d.Id()))

	if err := ioutil.WriteFile(path.Join(checkout_dir, filepath), []byte(contents), 0666); err != nil {
		return err
	}

	git_add := exec.Command("git", "add", "--intent-to-add", "--", filepath)
	git_add.Dir = checkout_dir
	if err := git_add.Run(); err != nil {
		return err
	}

	git_commit := exec.Command("git", "commit", "-m", commit_message, "--", filepath)
	git_commit.Dir = checkout_dir
	if err := git_commit.Run(); err != nil {
		return err
	}

	git_push := exec.Command("git", "push", repo, fmt.Sprintf("HEAD:%s", branch))
	git_push.Dir = checkout_dir
	if err := git_push.Run(); err != nil {
		return err
	}
	return nil
}
func FileDelete(d *schema.ResourceData, meta interface{}) error {
	return nil
}

func mungeGitDir(id string) string {
	return b64.URLEncoding.EncodeToString([]byte(id))
}

func shallowSparseGitCheckout(checkout_dir, repo, branch, filepath string) error {
	if err := os.MkdirAll(checkout_dir, 0755); err != nil {
		return err
	}

	// git init appears to be idempotent.
	git_init := exec.Command("git", "init")
	git_init.Dir = checkout_dir
	if err := git_init.Run(); err != nil {
		return err
	}

	git_config := exec.Command("git", "config", "core.sparsecheckout", "true")
	git_config.Dir = checkout_dir
	if err := git_config.Run(); err != nil {
		return err
	}

	if err := ioutil.WriteFile(
		path.Join(checkout_dir, ".git", "info", "sparse-checkout"),
		[]byte(filepath),
		0666,
	); err != nil {
		return err
	}

	git_fetch := exec.Command("git", "fetch", "--depth", "1", repo, branch)
	git_fetch.Dir = checkout_dir
	if err := git_fetch.Run(); err != nil {
		return err
	}

	git_checkout := exec.Command("git", "checkout", "--force", "FETCH_HEAD")
	git_checkout.Dir = checkout_dir
	if err := git_checkout.Run(); err != nil {
		return err
	}

	git_clean := exec.Command("git", "clean", "-ffdx")
	git_clean.Dir = checkout_dir
	if err := git_clean.Run(); err != nil {
		return err
	}

	return nil
}<|endoftext|>