<commit_before>package main

import (
	"context"
	golog "log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"sync/atomic"
	"syscall"
	"time"

	"github.com/NYTimes/gziphandler"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3iface"
	"github.com/go-redis/redis/v8"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/namsral/flag"
	"github.com/oxtoacart/bpool"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/h2c"

	"github.com/tilezen/tapalcatl/pkg/buffer"
	"github.com/tilezen/tapalcatl/pkg/cache"
	"github.com/tilezen/tapalcatl/pkg/config"
	"github.com/tilezen/tapalcatl/pkg/handler"
	"github.com/tilezen/tapalcatl/pkg/log"
	"github.com/tilezen/tapalcatl/pkg/metrics"
	"github.com/tilezen/tapalcatl/pkg/storage"
	"github.com/tilezen/tapalcatl/pkg/tile"
)

const (
	// The time to wait after responding /ready with non-200 before starting to shut down the HTTP server
	gracefulShutdownSleep = 20 * time.Second
	// The time to wait for the in-flight HTTP requests to complete before exiting
	gracefulShutdownTimeout = 5 * time.Second
)

func main() {
	var listen, healthcheck, readyCheck string
	var poolNumEntries, poolEntrySize int
	var metricsStatsdAddr, metricsStatsdPrefix string
	var redisAddr string

	hc := config.HandlerConfig{}

	systemLogger := golog.New(os.Stdout, "", golog.LstdFlags|golog.LUTC|golog.Lmicroseconds)
	hostname, err := os.Hostname()
	if err != nil {
		// NOTE: if there are legitimate cases when this can fail, we
		// can leave off the hostname in the logger.
		// But for now we prefer to get notified of it.
		systemLogger.Fatalf("ERROR: Cannot find hostname to use for logger")
	}
	// use this logger everywhere.
	logger := log.NewJsonLogger(systemLogger, hostname)

	f := flag.NewFlagSetWithEnvPrefix(os.Args[0], "TAPALCATL", 0)
	f.Var(&hc, "handler",
		`JSON object defining how request patterns will be handled.
	 Aws { Object present when Aws-wide configuration is needed, eg session config.
     Region string Name of aws region
   }
   Storage { key -> storage definition mapping
     storage name string -> {
        Type string storage type, can be "s3" or "file
        MetatileSize int      Number of 256px tiles in each dimension of the metatile.
        MetatileMaxDetailZoom int Maximum level of detail available in the metatiles.
        TileSize int        Size of tile in 256px tile units.

       (s3 storage)
        Layer      string   Name of layer to use in this bucket. Only relevant for s3.
        Bucket     string   Name of S3 bucket to fetch from.
        KeyPattern string   Pattern to fill with variables from the main pattern to make the S3 key.
        Healthcheck string Name of S3 key to use when querying health of S3 system.

       (file storage)
        BaseDir    string   Base directory to look for files under.
        Healthcheck string  Path to a file (inside BaseDir) when querying health of system.
     }
   }
   Pattern { request pattern -> storage configuration mapping
     request pattern string -> {
       storage string Name of storage defintion to use
       list of optional storage configuration to use:
         defaultPrefix is required for s3, others are optional overrides of relevant definition
         DefaultPrefix string  DefaultPrefix to use in this bucket.
     }
   }
   Mime { extension -> content-type used in http response
   }
`)
	f.StringVar(&listen, "listen", ":8080", "interface and port to listen on")
	f.String("config", "", "Config file to read values from.")
	f.StringVar(&healthcheck, "healthcheck", "", "A URL path for healthcheck. Intended for use by load balancer health checks.")
	f.StringVar(&readyCheck, "readycheck", "", "A URL path for readiness check. Intended for use by Kubernetes readinessProbe.")

	f.IntVar(&poolNumEntries, "poolnumentries", 0, "Number of buffers to pool.")
	f.IntVar(&poolEntrySize, "poolentrysize", 0, "Size of each buffer in pool.")

	f.StringVar(&metricsStatsdAddr, "metrics-statsd-addr", "", "host:port to use to send data to statsd")
	f.StringVar(&metricsStatsdPrefix, "metrics-statsd-prefix", "", "prefix to prepend to metrics")

	f.StringVar(&redisAddr, "redis-addr", "", "Redis connection address for caching purposes")

	err = f.Parse(os.Args[1:])
	if err == flag.ErrHelp {
		return
	} else if err != nil {
		logFatalCfgErr(logger, "Unable to parse input command line, environment or config: %s", err.Error())
	}

	if len(hc.Pattern) == 0 {
		logFatalCfgErr(logger, "You must provide at least one pattern.")
	}
	if len(hc.Storage) == 0 {
		logFatalCfgErr(logger, "You must provide at least one storage.")
	}

	r := mux.NewRouter()

	// buffer manager shared by all handlers
	var bufferManager buffer.BufferManager

	if poolNumEntries > 0 && poolEntrySize > 0 {
		bufferManager = bpool.NewSizedBufferPool(poolNumEntries, poolEntrySize)
	} else {
		bufferManager = &buffer.OnDemandBufferManager{}
	}

	var tileCache cache.Cache
	if redisAddr != "" {
		client := redis.NewClient(&redis.Options{
			Addr: redisAddr,
		})
		tileCache = cache.NewRedisCache(client)
		logger.Info("Configured Redis to connect to %s", redisAddr)
	} else {
		tileCache = cache.NilCache
	}

	// metrics writer configuration
	var mw metrics.MetricsWriter
	if metricsStatsdAddr != "" {
		udpAddr, err := net.ResolveUDPAddr("udp4", metricsStatsdAddr)
		if err != nil {
			logFatalCfgErr(logger, "Invalid metricsstatsdaddr %s: %s", metricsStatsdAddr, err)
		}
		mw = metrics.NewStatsdMetricsWriter(udpAddr, metricsStatsdPrefix, logger)
	} else {
		mw = &metrics.NilMetricsWriter{}
	}

	// set if we have s3 storage configured, and shared across all s3 sessions
	var awsSession *session.Session

	for sName, sd := range hc.Storage {
		t := sd.Type
		switch t {
		case "s3":
		case "file":
		default:
			logFatalCfgErr(logger, "Unknown storage type for storage %s: %s", sName, t)
		}
	}

	// keep track of the storages so we can healthcheck them
	// we only need to check unique type/healthcheck configurations
	healthCheckStorages := make(map[config.HealthCheckConfig]storage.Storage)

	// create the storage implementations and handler routes for patterns
	var stg storage.Storage
	for reqPattern, rhc := range hc.Pattern {

		storageDefinitionName := rhc.Storage
		sd, ok := hc.Storage[storageDefinitionName]
		if !ok {
			logFatalCfgErr(logger, "Unknown storage definition: %s", storageDefinitionName)
		}
		metatileSize := sd.MetatileSize
		if rhc.MetatileSize != nil {
			metatileSize = *rhc.MetatileSize
		}
		if !tile.IsPowerOfTwo(metatileSize) {
			logFatalCfgErr(logger, "Metatile size must be power of two, but %d is not", metatileSize)
		}

		tileSize := 1
		if sd.TileSize != nil {
			tileSize = *sd.TileSize
		}
		if rhc.TileSize != nil {
			tileSize = *rhc.TileSize
		}
		if !tile.IsPowerOfTwo(tileSize) {
			logFatalCfgErr(logger, "Tile size must be power of two, but %d is not", tileSize)
		}

		metatileMaxDetailZoom := 0
		if sd.MetatileMaxDetailZoom != nil {
			metatileMaxDetailZoom = *sd.MetatileMaxDetailZoom
		}

		layer := sd.Layer
		if rhc.Layer != nil {
			layer = *rhc.Layer
		}

		var healthcheck string

		switch sd.Type {
		case "s3":
			if rhc.DefaultPrefix == nil {
				logFatalCfgErr(logger, "S3 configuration requires defaultPrefix")
			}
			prefix := *rhc.DefaultPrefix

			if awsSession == nil {
				if hc.Aws != nil && hc.Aws.Region != nil {
					awsSession, err = session.NewSessionWithOptions(session.Options{
						Config:            aws.Config{Region: hc.Aws.Region},
						SharedConfigState: session.SharedConfigEnable,
					})
				} else {
					awsSession, err = session.NewSessionWithOptions(session.Options{
						SharedConfigState: session.SharedConfigEnable,
					})
				}
			}
			if err != nil {
				logFatalCfgErr(logger, "Unable to set up AWS session: %s", err.Error())
			}

			var s3Client s3iface.S3API
			if hc.Aws.Role != nil {
				creds := stscreds.NewCredentials(awsSession, *hc.Aws.Role)
				s3Client = s3.New(awsSession, &aws.Config{Credentials: creds})
			} else {
				s3Client = s3.New(awsSession)
			}

			keyPattern := sd.KeyPattern
			if rhc.KeyPattern != nil {
				keyPattern = *rhc.KeyPattern
			}

			if sd.Bucket == "" {
				logFatalCfgErr(logger, "S3 storage missing bucket configuration")
			}
			if keyPattern == "" {
				logFatalCfgErr(logger, "S3 storage missing key pattern")
			}

			if sd.Healthcheck == "" {
				logger.Warning(log.LogCategory_ConfigError, "Missing healthcheck for storage s3")
			}

			healthcheck = sd.Healthcheck
			stg = storage.NewS3Storage(s3Client, tileCache, sd.Bucket, keyPattern, prefix, layer, healthcheck)

		case "file":
			if sd.BaseDir == "" {
				logFatalCfgErr(logger, "File storage missing base dir")
			}

			if sd.Healthcheck == "" {
				logger.Warning(log.LogCategory_ConfigError, "Missing healthcheck for storage file")
			}

			healthcheck = sd.Healthcheck
			stg = storage.NewFileStorage(sd.BaseDir, tileCache, layer, healthcheck)

		default:
			logFatalCfgErr(logger, "Unknown storage type: %s", sd.Type)
		}

		if healthcheck != "" {
			storageErr := stg.HealthCheck()
			if storageErr != nil {
				logger.Warning(log.LogCategory_ConfigError, "Healthcheck failed on storage: %s", storageErr)
			}

			hcc := config.HealthCheckConfig{
				Type:        sd.Type,
				Healthcheck: healthcheck,
			}

			if _, ok := healthCheckStorages[hcc]; !ok {
				healthCheckStorages[hcc] = stg
			}
		}

		if rhc.Type == nil || *rhc.Type == "metatile" {
			parser := &handler.MetatileMuxParser{
				MimeMap: hc.Mime,
			}

			h := handler.MetatileHandler(parser, metatileSize, tileSize, metatileMaxDetailZoom, stg, bufferManager, mw, logger, tileCache)
			gzipped := gziphandler.GzipHandler(h)

			r.Handle(reqPattern, gzipped).Methods("GET")

		} else if rhc.Type != nil && *rhc.Type == "tilejson" {
			parser := &handler.TileJsonParser{}
			h := handler.TileJsonHandler(parser, stg, mw, logger)
			gzipped := gziphandler.GzipHandler(h)
			r.Handle(reqPattern, gzipped).Methods("GET")
		} else {
			systemLogger.Fatalf("ERROR: Invalid route handler type: %s\n", *rhc.Type)
		}

	}

	if hc.Preview != nil {
		if hc.Preview.Path == nil || hc.Preview.Template == nil {
			systemLogger.Fatalf("ERROR: Preview must have path and template specified")
		}

		var templateData map[string]interface{}
		if hc.Preview.Data != nil {
			templateData = *hc.Preview.Data
		}

		fileHandler, err := handler.NewFileHandler(*hc.Preview.Template, templateData)
		if err != nil {
			systemLogger.Fatalf("ERROR: Couldn't load preview template: %+v", err)
		}

		r.Handle(*hc.Preview.Path, fileHandler).Methods("GET")
	}

	if len(healthcheck) > 0 {
		storagesToCheck := make([]storage.Storage, len(healthCheckStorages))
		i := 0
		for _, s := range healthCheckStorages {
			storagesToCheck[i] = s
			i++
		}
		hc := handler.HealthCheckHandler(storagesToCheck, logger)
		r.Handle(healthcheck, hc).Methods("GET")
	}

	// Readiness probe for graceful shutdown support
	readinessResponseCode := uint32(http.StatusOK)
	if len(readyCheck) > 0 {
		r.HandleFunc(readyCheck, func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(int(atomic.LoadUint32(&readinessResponseCode)))
		})
	}

	corsHandler := handlers.CORS()(r)
	loggingHandler := log.LoggingMiddleware(logger)(corsHandler)

	logger.Info("Server started and listening on %s", listen)

	// Support for upgrading an http/1.1 connection to http/2
	// See https://github.com/thrawn01/h2c-golang-example
	http2Server := &http2.Server{}
	server := &http.Server{
		Addr:    listen,
		Handler: h2c.NewHandler(loggingHandler, http2Server),
	}

	// Code to handle shutdown gracefully
	shutdownChan := make(chan struct{})
	go func() {
		defer close(shutdownChan)

		// Wait for SIGTERM to come in
		signals := make(chan os.Signal, 1)
		signal.Notify(signals, syscall.SIGTERM)
		<-signals

		logger.Info("SIGTERM received. Starting graceful shutdown.")

		// Start failing readiness probes
		atomic.StoreUint32(&readinessResponseCode, http.StatusInternalServerError)
		// Wait for upstream clients
		time.Sleep(gracefulShutdownSleep)
		// Begin shutdown of in-flight requests
		shutdownCtx, shutdownCtxCancel := context.WithTimeout(context.Background(), gracefulShutdownTimeout)
		if err := server.Shutdown(shutdownCtx); err != nil {
			logger.Info("Error waiting for server shutdown: %+v", err)
		}
		shutdownCtxCancel()
	}()

	logger.Info("Service started")
	if err := server.ListenAndServe(); err != nil {
		logger.Info("Couldn't start HTTP server: %+v", err)
	}
	<-shutdownChan
}

func logFatalCfgErr(logger log.JsonLogger, msg string, xs ...interface{}) {
	logger.Error(log.LogCategory_ConfigError, msg, xs...)
	os.Exit(1)
}
<commit_msg>Ping redis at startup<commit_after>package main

import (
	"context"
	golog "log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"sync/atomic"
	"syscall"
	"time"

	"github.com/NYTimes/gziphandler"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3iface"
	"github.com/go-redis/redis/v8"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/namsral/flag"
	"github.com/oxtoacart/bpool"
	"golang.org/x/net/http2"
	"golang.org/x/net/http2/h2c"

	"github.com/tilezen/tapalcatl/pkg/buffer"
	"github.com/tilezen/tapalcatl/pkg/cache"
	"github.com/tilezen/tapalcatl/pkg/config"
	"github.com/tilezen/tapalcatl/pkg/handler"
	"github.com/tilezen/tapalcatl/pkg/log"
	"github.com/tilezen/tapalcatl/pkg/metrics"
	"github.com/tilezen/tapalcatl/pkg/storage"
	"github.com/tilezen/tapalcatl/pkg/tile"
)

const (
	// The time to wait after responding /ready with non-200 before starting to shut down the HTTP server
	gracefulShutdownSleep = 20 * time.Second
	// The time to wait for the in-flight HTTP requests to complete before exiting
	gracefulShutdownTimeout = 5 * time.Second
)

func main() {
	var listen, healthcheck, readyCheck string
	var poolNumEntries, poolEntrySize int
	var metricsStatsdAddr, metricsStatsdPrefix string
	var redisAddr string

	hc := config.HandlerConfig{}

	systemLogger := golog.New(os.Stdout, "", golog.LstdFlags|golog.LUTC|golog.Lmicroseconds)
	hostname, err := os.Hostname()
	if err != nil {
		// NOTE: if there are legitimate cases when this can fail, we
		// can leave off the hostname in the logger.
		// But for now we prefer to get notified of it.
		systemLogger.Fatalf("ERROR: Cannot find hostname to use for logger")
	}
	// use this logger everywhere.
	logger := log.NewJsonLogger(systemLogger, hostname)

	f := flag.NewFlagSetWithEnvPrefix(os.Args[0], "TAPALCATL", 0)
	f.Var(&hc, "handler",
		`JSON object defining how request patterns will be handled.
	 Aws { Object present when Aws-wide configuration is needed, eg session config.
     Region string Name of aws region
   }
   Storage { key -> storage definition mapping
     storage name string -> {
        Type string storage type, can be "s3" or "file
        MetatileSize int      Number of 256px tiles in each dimension of the metatile.
        MetatileMaxDetailZoom int Maximum level of detail available in the metatiles.
        TileSize int        Size of tile in 256px tile units.

       (s3 storage)
        Layer      string   Name of layer to use in this bucket. Only relevant for s3.
        Bucket     string   Name of S3 bucket to fetch from.
        KeyPattern string   Pattern to fill with variables from the main pattern to make the S3 key.
        Healthcheck string Name of S3 key to use when querying health of S3 system.

       (file storage)
        BaseDir    string   Base directory to look for files under.
        Healthcheck string  Path to a file (inside BaseDir) when querying health of system.
     }
   }
   Pattern { request pattern -> storage configuration mapping
     request pattern string -> {
       storage string Name of storage defintion to use
       list of optional storage configuration to use:
         defaultPrefix is required for s3, others are optional overrides of relevant definition
         DefaultPrefix string  DefaultPrefix to use in this bucket.
     }
   }
   Mime { extension -> content-type used in http response
   }
`)
	f.StringVar(&listen, "listen", ":8080", "interface and port to listen on")
	f.String("config", "", "Config file to read values from.")
	f.StringVar(&healthcheck, "healthcheck", "", "A URL path for healthcheck. Intended for use by load balancer health checks.")
	f.StringVar(&readyCheck, "readycheck", "", "A URL path for readiness check. Intended for use by Kubernetes readinessProbe.")

	f.IntVar(&poolNumEntries, "poolnumentries", 0, "Number of buffers to pool.")
	f.IntVar(&poolEntrySize, "poolentrysize", 0, "Size of each buffer in pool.")

	f.StringVar(&metricsStatsdAddr, "metrics-statsd-addr", "", "host:port to use to send data to statsd")
	f.StringVar(&metricsStatsdPrefix, "metrics-statsd-prefix", "", "prefix to prepend to metrics")

	f.StringVar(&redisAddr, "redis-addr", "", "Redis connection address for caching purposes")

	err = f.Parse(os.Args[1:])
	if err == flag.ErrHelp {
		return
	} else if err != nil {
		logFatalCfgErr(logger, "Unable to parse input command line, environment or config: %s", err.Error())
	}

	if len(hc.Pattern) == 0 {
		logFatalCfgErr(logger, "You must provide at least one pattern.")
	}
	if len(hc.Storage) == 0 {
		logFatalCfgErr(logger, "You must provide at least one storage.")
	}

	r := mux.NewRouter()

	// buffer manager shared by all handlers
	var bufferManager buffer.BufferManager

	if poolNumEntries > 0 && poolEntrySize > 0 {
		bufferManager = bpool.NewSizedBufferPool(poolNumEntries, poolEntrySize)
	} else {
		bufferManager = &buffer.OnDemandBufferManager{}
	}

	var tileCache cache.Cache
	if redisAddr != "" {
		client := redis.NewClient(&redis.Options{
			Addr: redisAddr,
		})

		// Ping Redis to make sure it's available before starting.
		// Using a longer timeout to give time for network connections to spin up, etc.
		timeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
		cancel()
		if err := client.Ping(timeoutCtx).Err(); err != nil {
			logFatalCfgErr(logger, "Couldn't reach Redis service at %s: %s", redisAddr, err.Error())
		}

		logger.Info("Redis connected to %s", redisAddr)
		tileCache = cache.NewRedisCache(client)
	} else {
		tileCache = cache.NilCache
	}

	// metrics writer configuration
	var mw metrics.MetricsWriter
	if metricsStatsdAddr != "" {
		udpAddr, err := net.ResolveUDPAddr("udp4", metricsStatsdAddr)
		if err != nil {
			logFatalCfgErr(logger, "Invalid metricsstatsdaddr %s: %s", metricsStatsdAddr, err)
		}
		mw = metrics.NewStatsdMetricsWriter(udpAddr, metricsStatsdPrefix, logger)
	} else {
		mw = &metrics.NilMetricsWriter{}
	}

	// set if we have s3 storage configured, and shared across all s3 sessions
	var awsSession *session.Session

	for sName, sd := range hc.Storage {
		t := sd.Type
		switch t {
		case "s3":
		case "file":
		default:
			logFatalCfgErr(logger, "Unknown storage type for storage %s: %s", sName, t)
		}
	}

	// keep track of the storages so we can healthcheck them
	// we only need to check unique type/healthcheck configurations
	healthCheckStorages := make(map[config.HealthCheckConfig]storage.Storage)

	// create the storage implementations and handler routes for patterns
	var stg storage.Storage
	for reqPattern, rhc := range hc.Pattern {

		storageDefinitionName := rhc.Storage
		sd, ok := hc.Storage[storageDefinitionName]
		if !ok {
			logFatalCfgErr(logger, "Unknown storage definition: %s", storageDefinitionName)
		}
		metatileSize := sd.MetatileSize
		if rhc.MetatileSize != nil {
			metatileSize = *rhc.MetatileSize
		}
		if !tile.IsPowerOfTwo(metatileSize) {
			logFatalCfgErr(logger, "Metatile size must be power of two, but %d is not", metatileSize)
		}

		tileSize := 1
		if sd.TileSize != nil {
			tileSize = *sd.TileSize
		}
		if rhc.TileSize != nil {
			tileSize = *rhc.TileSize
		}
		if !tile.IsPowerOfTwo(tileSize) {
			logFatalCfgErr(logger, "Tile size must be power of two, but %d is not", tileSize)
		}

		metatileMaxDetailZoom := 0
		if sd.MetatileMaxDetailZoom != nil {
			metatileMaxDetailZoom = *sd.MetatileMaxDetailZoom
		}

		layer := sd.Layer
		if rhc.Layer != nil {
			layer = *rhc.Layer
		}

		var healthcheck string

		switch sd.Type {
		case "s3":
			if rhc.DefaultPrefix == nil {
				logFatalCfgErr(logger, "S3 configuration requires defaultPrefix")
			}
			prefix := *rhc.DefaultPrefix

			if awsSession == nil {
				if hc.Aws != nil && hc.Aws.Region != nil {
					awsSession, err = session.NewSessionWithOptions(session.Options{
						Config:            aws.Config{Region: hc.Aws.Region},
						SharedConfigState: session.SharedConfigEnable,
					})
				} else {
					awsSession, err = session.NewSessionWithOptions(session.Options{
						SharedConfigState: session.SharedConfigEnable,
					})
				}
			}
			if err != nil {
				logFatalCfgErr(logger, "Unable to set up AWS session: %s", err.Error())
			}

			var s3Client s3iface.S3API
			if hc.Aws.Role != nil {
				creds := stscreds.NewCredentials(awsSession, *hc.Aws.Role)
				s3Client = s3.New(awsSession, &aws.Config{Credentials: creds})
			} else {
				s3Client = s3.New(awsSession)
			}

			keyPattern := sd.KeyPattern
			if rhc.KeyPattern != nil {
				keyPattern = *rhc.KeyPattern
			}

			if sd.Bucket == "" {
				logFatalCfgErr(logger, "S3 storage missing bucket configuration")
			}
			if keyPattern == "" {
				logFatalCfgErr(logger, "S3 storage missing key pattern")
			}

			if sd.Healthcheck == "" {
				logger.Warning(log.LogCategory_ConfigError, "Missing healthcheck for storage s3")
			}

			healthcheck = sd.Healthcheck
			stg = storage.NewS3Storage(s3Client, tileCache, sd.Bucket, keyPattern, prefix, layer, healthcheck)

		case "file":
			if sd.BaseDir == "" {
				logFatalCfgErr(logger, "File storage missing base dir")
			}

			if sd.Healthcheck == "" {
				logger.Warning(log.LogCategory_ConfigError, "Missing healthcheck for storage file")
			}

			healthcheck = sd.Healthcheck
			stg = storage.NewFileStorage(sd.BaseDir, tileCache, layer, healthcheck)

		default:
			logFatalCfgErr(logger, "Unknown storage type: %s", sd.Type)
		}

		if healthcheck != "" {
			storageErr := stg.HealthCheck()
			if storageErr != nil {
				logger.Warning(log.LogCategory_ConfigError, "Healthcheck failed on storage: %s", storageErr)
			}

			hcc := config.HealthCheckConfig{
				Type:        sd.Type,
				Healthcheck: healthcheck,
			}

			if _, ok := healthCheckStorages[hcc]; !ok {
				healthCheckStorages[hcc] = stg
			}
		}

		if rhc.Type == nil || *rhc.Type == "metatile" {
			parser := &handler.MetatileMuxParser{
				MimeMap: hc.Mime,
			}

			h := handler.MetatileHandler(parser, metatileSize, tileSize, metatileMaxDetailZoom, stg, bufferManager, mw, logger, tileCache)
			gzipped := gziphandler.GzipHandler(h)

			r.Handle(reqPattern, gzipped).Methods("GET")

		} else if rhc.Type != nil && *rhc.Type == "tilejson" {
			parser := &handler.TileJsonParser{}
			h := handler.TileJsonHandler(parser, stg, mw, logger)
			gzipped := gziphandler.GzipHandler(h)
			r.Handle(reqPattern, gzipped).Methods("GET")
		} else {
			systemLogger.Fatalf("ERROR: Invalid route handler type: %s\n", *rhc.Type)
		}

	}

	if hc.Preview != nil {
		if hc.Preview.Path == nil || hc.Preview.Template == nil {
			systemLogger.Fatalf("ERROR: Preview must have path and template specified")
		}

		var templateData map[string]interface{}
		if hc.Preview.Data != nil {
			templateData = *hc.Preview.Data
		}

		fileHandler, err := handler.NewFileHandler(*hc.Preview.Template, templateData)
		if err != nil {
			systemLogger.Fatalf("ERROR: Couldn't load preview template: %+v", err)
		}

		r.Handle(*hc.Preview.Path, fileHandler).Methods("GET")
	}

	if len(healthcheck) > 0 {
		storagesToCheck := make([]storage.Storage, len(healthCheckStorages))
		i := 0
		for _, s := range healthCheckStorages {
			storagesToCheck[i] = s
			i++
		}
		hc := handler.HealthCheckHandler(storagesToCheck, logger)
		r.Handle(healthcheck, hc).Methods("GET")
	}

	// Readiness probe for graceful shutdown support
	readinessResponseCode := uint32(http.StatusOK)
	if len(readyCheck) > 0 {
		r.HandleFunc(readyCheck, func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(int(atomic.LoadUint32(&readinessResponseCode)))
		})
	}

	corsHandler := handlers.CORS()(r)
	loggingHandler := log.LoggingMiddleware(logger)(corsHandler)

	logger.Info("Server started and listening on %s", listen)

	// Support for upgrading an http/1.1 connection to http/2
	// See https://github.com/thrawn01/h2c-golang-example
	http2Server := &http2.Server{}
	server := &http.Server{
		Addr:    listen,
		Handler: h2c.NewHandler(loggingHandler, http2Server),
	}

	// Code to handle shutdown gracefully
	shutdownChan := make(chan struct{})
	go func() {
		defer close(shutdownChan)

		// Wait for SIGTERM to come in
		signals := make(chan os.Signal, 1)
		signal.Notify(signals, syscall.SIGTERM)
		<-signals

		logger.Info("SIGTERM received. Starting graceful shutdown.")

		// Start failing readiness probes
		atomic.StoreUint32(&readinessResponseCode, http.StatusInternalServerError)
		// Wait for upstream clients
		time.Sleep(gracefulShutdownSleep)
		// Begin shutdown of in-flight requests
		shutdownCtx, shutdownCtxCancel := context.WithTimeout(context.Background(), gracefulShutdownTimeout)
		if err := server.Shutdown(shutdownCtx); err != nil {
			logger.Info("Error waiting for server shutdown: %+v", err)
		}
		shutdownCtxCancel()
	}()

	logger.Info("Service started")
	if err := server.ListenAndServe(); err != nil {
		logger.Info("Couldn't start HTTP server: %+v", err)
	}
	<-shutdownChan
}

func logFatalCfgErr(logger log.JsonLogger, msg string, xs ...interface{}) {
	logger.Error(log.LogCategory_ConfigError, msg, xs...)
	os.Exit(1)
}
<|endoftext|>


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

package cmd

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"
	"sort"
	"strings"
	"syscall"

	"launchpad.net/gnuflag"
)

var errUndefinedTarget = errors.New(`No target defined. Please use target-add/target-set to define a target.

For more details, please run "tsuru help target".`)

type tsuruTarget struct {
	label, url string
}

func (t *tsuruTarget) String() string {
	return t.label + " (" + t.url + ")"
}

type targetSlice struct {
	targets []tsuruTarget
	current int
	sorted  bool
}

func newTargetSlice() *targetSlice {
	return &targetSlice{current: -1}
}

func (t *targetSlice) add(label, url string) {
	t.targets = append(t.targets, tsuruTarget{label: label, url: url})
	length := t.Len()
	if length > 1 && !t.Less(t.Len()-2, t.Len()-1) {
		t.sorted = false
	}
}

func (t *targetSlice) Len() int {
	return len(t.targets)
}

func (t *targetSlice) Less(i, j int) bool {
	return t.targets[i].label < t.targets[j].label
}

func (t *targetSlice) Swap(i, j int) {
	t.targets[i], t.targets[j] = t.targets[j], t.targets[i]
}

func (t *targetSlice) Sort() {
	sort.Sort(t)
	t.sorted = true
}

func (t *targetSlice) setCurrent(url string) {
	if !t.sorted {
		t.Sort()
	}
	for i, target := range t.targets {
		if target.url == url {
			t.current = i
			break
		}
	}
}

func (t *targetSlice) String() string {
	if !t.sorted {
		t.Sort()
	}
	values := make([]string, len(t.targets))
	for i, target := range t.targets {
		prefix := "  "
		if t.current == i {
			prefix = "* "
		}
		values[i] = prefix + target.String()
	}
	return strings.Join(values, "\n")
}

func ReadTarget() (string, error) {
	if target := os.Getenv("TSURU_TARGET"); target != "" {
		return target, nil
	}
	targetPath := JoinWithUserDir(".tsuru", "target")
	if f, err := filesystem().Open(targetPath); err == nil {
		defer f.Close()
		if b, err := ioutil.ReadAll(f); err == nil {
			return strings.TrimSpace(string(b)), nil
		}
	}
	return "", errUndefinedTarget
}

func deleteTargetFile() {
	filesystem().Remove(JoinWithUserDir(".tsuru", "target"))
}

func GetURL(path string) (string, error) {
	var prefix string
	target, err := ReadTarget()
	if err != nil {
		return "", err
	}
	if m, _ := regexp.MatchString("^https?://", target); !m {
		prefix = "http://"
	}
	return prefix + strings.TrimRight(target, "/") + path, nil
}

func writeTarget(t string) error {
	targetPath := JoinWithUserDir(".tsuru", "target")
	targetFile, err := filesystem().OpenFile(targetPath, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	defer targetFile.Close()
	n, err := targetFile.WriteString(t)
	if n != len(t) || err != nil {
		return errors.New("Failed to write the target file")
	}
	return nil
}

type targetAdd struct {
	fs  *gnuflag.FlagSet
	set bool
}

func (t *targetAdd) Info() *Info {
	return &Info{
		Name:    "target-add",
		Usage:   "target-add <label> <target> [--set-current|-s]",
		Desc:    "Adds a new entry to the list of available targets",
		MinArgs: 2,
	}
}

func (t *targetAdd) Run(ctx *Context, client *Client) error {
	var target string
	var label string
	if len(ctx.Args) != 2 {
		return errors.New("Invalid arguments")
	}
	label = ctx.Args[0]
	target = ctx.Args[1]
	err := writeOnTargetList(label, target)
	if err != nil {
		return err
	}
	fmt.Fprintf(ctx.Stdout, "New target %s -> %s added to target list", label, target)
	if t.set {
		writeTarget(target)
		fmt.Fprint(ctx.Stdout, " and defined as the current target")
	}
	fmt.Fprintln(ctx.Stdout)
	return nil
}

func (t *targetAdd) Flags() *gnuflag.FlagSet {
	if t.fs == nil {
		t.fs = gnuflag.NewFlagSet("target-add", gnuflag.ExitOnError)
		t.fs.BoolVar(&t.set, "set-current", false, "Add and define the target as the current target")
		t.fs.BoolVar(&t.set, "s", false, "Add and define the target as the current target")
	}
	return t.fs
}

func resetTargetList() error {
	targetsPath := JoinWithUserDir(".tsuru", "targets")
	targetsFile, err := filesystem().OpenFile(targetsPath, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	defer targetsFile.Close()
	return nil
}

func writeOnTargetList(label string, target string) error {
	label = strings.TrimSpace(label)
	target = strings.TrimSpace(target)
	targetExist, err := checkIfTargetLabelExists(label)
	if err != nil {
		return err
	}
	if targetExist {
		return errors.New("Target label provided already exist")
	}
	targetsPath := JoinWithUserDir(".tsuru", "targets")
	targetsFile, err := filesystem().OpenFile(targetsPath, syscall.O_RDWR|syscall.O_CREAT|syscall.O_APPEND, 0600)
	if err != nil {
		return err
	}
	defer targetsFile.Close()
	content := label + "\t" + target + "\n"
	n, err := targetsFile.WriteString(content)
	if n != len(content) || err != nil {
		return errors.New("Failed to write the target file")
	}
	return nil
}

func checkIfTargetLabelExists(label string) (bool, error) {
	targets, err := getTargets()
	if err != nil {
		return false, err
	}
	_, exists := targets[label]
	if exists {
		return true, nil
	}
	return false, nil
}

func getTargets() (map[string]string, error) {
	var targets = map[string]string{}
	targetsPath := JoinWithUserDir(".tsuru", "targets")
	if f, err := filesystem().Open(targetsPath); err == nil {
		defer f.Close()
		if b, err := ioutil.ReadAll(f); err == nil {
			var targetLines = strings.Split(strings.TrimSpace(string(b)), "\n")

			for i := range targetLines {
				var targetSplt = strings.Split(targetLines[i], "\t")

				if len(targetSplt) == 2 {
					targets[targetSplt[0]] = targetSplt[1]
				}
			}
		}
	}
	return targets, nil
}

type targetList struct{}

func (t *targetList) Info() *Info {
	desc := `Displays the list of targets, marking the current.

Other commands related to target:

  - target-add: adds a new target to the list of targets
  - target-set: defines one of the targets in the list as the current target
  - target-remove: removes one target from the list`
	return &Info{
		Name:    "target-list",
		Usage:   "target-list",
		Desc:    desc,
		MinArgs: 0,
	}
}

func (t *targetList) Run(ctx *Context, client *Client) error {
	slice := newTargetSlice()
	targets, err := getTargets()
	if err != nil {
		return err
	}
	for label, target := range targets {
		slice.add(label, target)
	}
	if current, err := ReadTarget(); err == nil {
		slice.setCurrent(current)
	}
	fmt.Fprintf(ctx.Stdout, "%s\n", slice)
	return nil
}

type targetRemove struct{}

func (t *targetRemove) Info() *Info {
	desc := `Remove a target from target-list (tsuru server)
`
	return &Info{
		Name:    "target-remove",
		Usage:   "target-remove",
		Desc:    desc,
		MinArgs: 1,
	}
}

func (t *targetRemove) Run(ctx *Context, client *Client) error {
	if len(ctx.Args) != 1 {
		return errors.New("Invalid arguments")
	}
	targetLabelToRemove := strings.TrimSpace(ctx.Args[0])
	targets, err := getTargets()
	if err != nil {
		return err
	}
	var turl string
	for label, url := range targets {
		if label == targetLabelToRemove {
			turl = url
			delete(targets, label)
		}
	}
	if turl != "" {
		var current string
		if current, err = ReadTarget(); err == nil && current == turl {
			deleteTargetFile()
		}
	}
	err = resetTargetList()
	if err != nil {
		return err
	}
	for label, target := range targets {
		writeOnTargetList(label, target)
	}
	return nil
}

type targetSet struct{}

func (t *targetSet) Info() *Info {
	desc := `Change current target (tsuru server)
`
	return &Info{
		Name:    "target-set",
		Usage:   "target-set <label>",
		Desc:    desc,
		MinArgs: 1,
	}
}

func (t *targetSet) Run(ctx *Context, client *Client) error {
	if len(ctx.Args) != 1 {
		return errors.New("Invalid arguments")
	}
	targetLabelToSet := strings.TrimSpace(ctx.Args[0])
	labelExist, err := checkIfTargetLabelExists(targetLabelToSet)
	if !labelExist {
		return errors.New("Target not found")
	}
	targets, err := getTargets()
	if err != nil {
		return err
	}
	for label, target := range targets {
		if label == targetLabelToSet {
			err = writeTarget(target)
			if err != nil {
				return err
			}
			fmt.Fprintf(ctx.Stdout, "New target is %s -> %s\n", label, target)
		}
	}
	return nil
}
<commit_msg>Creating .tsuru directory before write a target<commit_after>// Copyright 2015 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cmd

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"
	"sort"
	"strings"
	"syscall"

	"launchpad.net/gnuflag"
)

var errUndefinedTarget = errors.New(`No target defined. Please use target-add/target-set to define a target.

For more details, please run "tsuru help target".`)

type tsuruTarget struct {
	label, url string
}

func (t *tsuruTarget) String() string {
	return t.label + " (" + t.url + ")"
}

type targetSlice struct {
	targets []tsuruTarget
	current int
	sorted  bool
}

func newTargetSlice() *targetSlice {
	return &targetSlice{current: -1}
}

func (t *targetSlice) add(label, url string) {
	t.targets = append(t.targets, tsuruTarget{label: label, url: url})
	length := t.Len()
	if length > 1 && !t.Less(t.Len()-2, t.Len()-1) {
		t.sorted = false
	}
}

func (t *targetSlice) Len() int {
	return len(t.targets)
}

func (t *targetSlice) Less(i, j int) bool {
	return t.targets[i].label < t.targets[j].label
}

func (t *targetSlice) Swap(i, j int) {
	t.targets[i], t.targets[j] = t.targets[j], t.targets[i]
}

func (t *targetSlice) Sort() {
	sort.Sort(t)
	t.sorted = true
}

func (t *targetSlice) setCurrent(url string) {
	if !t.sorted {
		t.Sort()
	}
	for i, target := range t.targets {
		if target.url == url {
			t.current = i
			break
		}
	}
}

func (t *targetSlice) String() string {
	if !t.sorted {
		t.Sort()
	}
	values := make([]string, len(t.targets))
	for i, target := range t.targets {
		prefix := "  "
		if t.current == i {
			prefix = "* "
		}
		values[i] = prefix + target.String()
	}
	return strings.Join(values, "\n")
}

func ReadTarget() (string, error) {
	if target := os.Getenv("TSURU_TARGET"); target != "" {
		return target, nil
	}
	targetPath := JoinWithUserDir(".tsuru", "target")
	if f, err := filesystem().Open(targetPath); err == nil {
		defer f.Close()
		if b, err := ioutil.ReadAll(f); err == nil {
			return strings.TrimSpace(string(b)), nil
		}
	}
	return "", errUndefinedTarget
}

func deleteTargetFile() {
	filesystem().Remove(JoinWithUserDir(".tsuru", "target"))
}

func GetURL(path string) (string, error) {
	var prefix string
	target, err := ReadTarget()
	if err != nil {
		return "", err
	}
	if m, _ := regexp.MatchString("^https?://", target); !m {
		prefix = "http://"
	}
	return prefix + strings.TrimRight(target, "/") + path, nil
}

func writeTarget(t string) error {
	targetPath := JoinWithUserDir(".tsuru", "target")
	targetFile, err := filesystem().OpenFile(targetPath, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	defer targetFile.Close()
	n, err := targetFile.WriteString(t)
	if n != len(t) || err != nil {
		return errors.New("Failed to write the target file")
	}
	return nil
}

type targetAdd struct {
	fs  *gnuflag.FlagSet
	set bool
}

func (t *targetAdd) Info() *Info {
	return &Info{
		Name:    "target-add",
		Usage:   "target-add <label> <target> [--set-current|-s]",
		Desc:    "Adds a new entry to the list of available targets",
		MinArgs: 2,
	}
}

func (t *targetAdd) Run(ctx *Context, client *Client) error {
	var target string
	var label string
	if len(ctx.Args) != 2 {
		return errors.New("Invalid arguments")
	}
	label = ctx.Args[0]
	target = ctx.Args[1]
	err := writeOnTargetList(label, target)
	if err != nil {
		return err
	}
	fmt.Fprintf(ctx.Stdout, "New target %s -> %s added to target list", label, target)
	if t.set {
		writeTarget(target)
		fmt.Fprint(ctx.Stdout, " and defined as the current target")
	}
	fmt.Fprintln(ctx.Stdout)
	return nil
}

func (t *targetAdd) Flags() *gnuflag.FlagSet {
	if t.fs == nil {
		t.fs = gnuflag.NewFlagSet("target-add", gnuflag.ExitOnError)
		t.fs.BoolVar(&t.set, "set-current", false, "Add and define the target as the current target")
		t.fs.BoolVar(&t.set, "s", false, "Add and define the target as the current target")
	}
	return t.fs
}

func resetTargetList() error {
	targetsPath := JoinWithUserDir(".tsuru", "targets")
	targetsFile, err := filesystem().OpenFile(targetsPath, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	defer targetsFile.Close()
	return nil
}

func writeOnTargetList(label string, target string) error {
	label = strings.TrimSpace(label)
	target = strings.TrimSpace(target)
	targetExist, err := checkIfTargetLabelExists(label)
	if err != nil {
		return err
	}
	if targetExist {
		return errors.New("Target label provided already exist")
	}
	targetsPath := JoinWithUserDir(".tsuru", "targets")
	targetsFile, err := filesystem().OpenFile(targetsPath, syscall.O_RDWR|syscall.O_CREAT|syscall.O_APPEND, 0600)
	if err != nil {
		return err
	}
	defer targetsFile.Close()
	content := label + "\t" + target + "\n"
	n, err := targetsFile.WriteString(content)
	if n != len(content) || err != nil {
		return errors.New("Failed to write the target file")
	}
	return nil
}

func checkIfTargetLabelExists(label string) (bool, error) {
	targets, err := getTargets()
	if err != nil {
		return false, err
	}
	_, exists := targets[label]
	if exists {
		return true, nil
	}
	return false, nil
}

func getTargets() (map[string]string, error) {
	var targets = map[string]string{}
	targetsPath := JoinWithUserDir(".tsuru", "targets")
	if _, err := os.Stat(JoinWithUserDir(".tsuru")); os.IsNotExist(err) {
		os.Mkdir(JoinWithUserDir(".tsuru"), 0700)
	}
	if f, err := filesystem().Open(targetsPath); err == nil {
		defer f.Close()
		if b, err := ioutil.ReadAll(f); err == nil {
			var targetLines = strings.Split(strings.TrimSpace(string(b)), "\n")

			for i := range targetLines {
				var targetSplt = strings.Split(targetLines[i], "\t")

				if len(targetSplt) == 2 {
					targets[targetSplt[0]] = targetSplt[1]
				}
			}
		}
	}
	return targets, nil
}

type targetList struct{}

func (t *targetList) Info() *Info {
	desc := `Displays the list of targets, marking the current.

Other commands related to target:

  - target-add: adds a new target to the list of targets
  - target-set: defines one of the targets in the list as the current target
  - target-remove: removes one target from the list`
	return &Info{
		Name:    "target-list",
		Usage:   "target-list",
		Desc:    desc,
		MinArgs: 0,
	}
}

func (t *targetList) Run(ctx *Context, client *Client) error {
	slice := newTargetSlice()
	targets, err := getTargets()
	if err != nil {
		return err
	}
	for label, target := range targets {
		slice.add(label, target)
	}
	if current, err := ReadTarget(); err == nil {
		slice.setCurrent(current)
	}
	fmt.Fprintf(ctx.Stdout, "%s\n", slice)
	return nil
}

type targetRemove struct{}

func (t *targetRemove) Info() *Info {
	desc := `Remove a target from target-list (tsuru server)
`
	return &Info{
		Name:    "target-remove",
		Usage:   "target-remove",
		Desc:    desc,
		MinArgs: 1,
	}
}

func (t *targetRemove) Run(ctx *Context, client *Client) error {
	if len(ctx.Args) != 1 {
		return errors.New("Invalid arguments")
	}
	targetLabelToRemove := strings.TrimSpace(ctx.Args[0])
	targets, err := getTargets()
	if err != nil {
		return err
	}
	var turl string
	for label, url := range targets {
		if label == targetLabelToRemove {
			turl = url
			delete(targets, label)
		}
	}
	if turl != "" {
		var current string
		if current, err = ReadTarget(); err == nil && current == turl {
			deleteTargetFile()
		}
	}
	err = resetTargetList()
	if err != nil {
		return err
	}
	for label, target := range targets {
		writeOnTargetList(label, target)
	}
	return nil
}

type targetSet struct{}

func (t *targetSet) Info() *Info {
	desc := `Change current target (tsuru server)
`
	return &Info{
		Name:    "target-set",
		Usage:   "target-set <label>",
		Desc:    desc,
		MinArgs: 1,
	}
}

func (t *targetSet) Run(ctx *Context, client *Client) error {
	if len(ctx.Args) != 1 {
		return errors.New("Invalid arguments")
	}
	targetLabelToSet := strings.TrimSpace(ctx.Args[0])
	labelExist, err := checkIfTargetLabelExists(targetLabelToSet)
	if !labelExist {
		return errors.New("Target not found")
	}
	targets, err := getTargets()
	if err != nil {
		return err
	}
	for label, target := range targets {
		if label == targetLabelToSet {
			err = writeTarget(target)
			if err != nil {
				return err
			}
			fmt.Fprintf(ctx.Stdout, "New target is %s -> %s\n", label, target)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"fmt"
	"io/ioutil"

	"github.com/skuid/helm-value-store/dynamo"
	"github.com/spf13/cobra"
)

type updateCmdArgs struct {
	table string
	uuid  string

	file    string
	labels  selectorSet
	version string
}

var updateArgs = &updateCmdArgs{}

var updateCmd = &cobra.Command{
	Use:   "update",
	Short: "update a release in the release store",
	Long:  "Update a release. Any specified fields (other than UUID) will overwrite the existing fields.",
	Run:   update,
}

func init() {
	RootCmd.AddCommand(updateCmd)
	updateCmd.Flags().StringVar(&updateArgs.table, "table", "helm-charts", "Name of table")
	updateCmd.Flags().StringVar(&updateArgs.uuid, "uuid", "", "The UUID of the release")
	updateCmd.Flags().StringVarP(&updateArgs.file, "file", "f", "", "Name of values file")
	updateCmd.Flags().VarP(&updateArgs.labels, "labels", "l", `The labels to apply. Each label should have the format "k=v".
    	Can be specified multiple times, or a comma-separated list.`)
	updateCmd.Flags().StringVar(&updateArgs.version, "version", "", "Version of the release")

	updateCmd.MarkFlagRequired("uuid")
	err := updateCmd.MarkFlagFilename("file", valueExtensions...)
	if err != nil {
		exitOnErr(err)
	}
}

func update(cmd *cobra.Command, args []string) {

	rs, err := dynamo.NewReleaseStore(updateArgs.table)
	exitOnErr(err)

	if len(updateArgs.uuid) == 0 {
		exitOnErr(errors.New("Must supply a UUID!"))
	}
	release, err := rs.Get(updateArgs.uuid)
	exitOnErr(err)

	if len(updateArgs.file) > 0 {
		values, err := ioutil.ReadFile(updateArgs.file)
		exitOnErr(err)
		release.Values = string(values)
	}

	if len(updateArgs.labels) > 0 {
		release.Labels = updateArgs.labels.ToMap()
	}
	if len(updateArgs.version) > 0 {
		release.Version = updateArgs.version
	}

	err = rs.Put(*release)
	exitOnErr(err)
	fmt.Printf("Updated release %s in release store!\n", release.Name)
}
<commit_msg>Add `--set` option to upgrade command<commit_after>package cmd

import (
	"errors"
	"fmt"
	"io/ioutil"

	"github.com/skuid/helm-value-store/dynamo"
	"github.com/spf13/cobra"
)

type updateCmdArgs struct {
	table string
	uuid  string

	file    string
	values  []string
	labels  selectorSet
	version string
}

var updateArgs = &updateCmdArgs{}

var updateCmd = &cobra.Command{
	Use:   "update",
	Short: "update a release in the release store",
	Long:  "Update a release. Any specified fields (other than UUID) will overwrite the existing fields.",
	Run:   update,
}

func init() {
	RootCmd.AddCommand(updateCmd)
	f := updateCmd.Flags()
	f.StringVar(&updateArgs.table, "table", "helm-charts", "Name of table")
	f.StringVar(&updateArgs.uuid, "uuid", "", "The UUID of the release")
	f.StringVarP(&updateArgs.file, "file", "f", "", "Name of values file. All values for this release will be overwritten with these values.")
	f.StringArrayVar(&updateArgs.values, "set", []string{}, `set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2).
    	These values will be merged with the existing values in the store or with the values file provided by --file.`)
	f.VarP(&updateArgs.labels, "labels", "l", `The labels to apply. Each label should have the format "k=v".
    	Can be specified multiple times, or a comma-separated list.`)
	f.StringVar(&updateArgs.version, "version", "", "Version of the release")

	updateCmd.MarkFlagRequired("uuid")
	err := updateCmd.MarkFlagFilename("file", valueExtensions...)
	if err != nil {
		exitOnErr(err)
	}
}

func update(cmd *cobra.Command, args []string) {

	rs, err := dynamo.NewReleaseStore(updateArgs.table)
	exitOnErr(err)

	if len(updateArgs.uuid) == 0 {
		exitOnErr(errors.New("Must supply a UUID!"))
	}
	release, err := rs.Get(updateArgs.uuid)
	exitOnErr(err)

	if len(updateArgs.file) > 0 {
		values, err := ioutil.ReadFile(updateArgs.file)
		exitOnErr(err)
		release.Values = string(values)
	}

	if len(updateArgs.values) > 0 {
		err := release.MergeValues(updateArgs.values)
		exitOnErr(err)
	}

	if len(updateArgs.labels) > 0 {
		release.Labels = updateArgs.labels.ToMap()
	}
	if len(updateArgs.version) > 0 {
		release.Version = updateArgs.version
	}

	err = rs.Put(*release)
	exitOnErr(err)
	fmt.Printf("Updated release %s in release store!\n", release.Name)
}
<|endoftext|>


<commit_before>package shell

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"reflect"
	"strings"
	"sync"
	"syscall"

	"github.com/zetamatta/go-findfile"

	"../dos"
	. "../ifdbg"
)

const FLAG_AMP2NEWCONSOLE = false

var WildCardExpansionAlways = false

type CommandNotFound struct {
	Name string
	Err  error
}

// from "TDM-GCC-64/x86_64-w64-mingw32/include/winbase.h"
const (
	CREATE_NEW_CONSOLE       = 0x10
	CREATE_NEW_PROCESS_GROUP = 0x200
)

func (this CommandNotFound) Stringer() string {
	return fmt.Sprintf("'%s' is not recognized as an internal or external command,\noperable program or batch file", this.Name)
}

func (this CommandNotFound) Error() string {
	return this.Stringer()
}

type Cmd struct {
	exec.Cmd
	Stdio        [3]*os.File
	HookCount    int
	Tag          interface{}
	PipeSeq      [2]uint
	IsBackGround bool
	RawArgs      []string

	OnFork  func(*Cmd) error
	OffFork func(*Cmd) error
	Closers []io.Closer
}

func (this *Cmd) GetRawArgs() []string {
	return this.RawArgs
}

func (this *Cmd) Close() {
	if this.Closers != nil {
		for _, c := range this.Closers {
			c.Close()
		}
		this.Closers = nil
	}
}

func New() *Cmd {
	this := Cmd{
		Stdio: [3]*os.File{os.Stdin, os.Stdout, os.Stderr},
	}
	this.Stdin = os.Stdin
	this.Stdout = os.Stdout
	this.Stderr = os.Stderr
	this.PipeSeq[0] = pipeSeq
	this.PipeSeq[1] = 0
	return &this
}

func (this *Cmd) SetStdin(f *os.File) {
	this.Stdio[0] = f
	this.Stdin = f
}
func (this *Cmd) SetStdout(f *os.File) {
	this.Stdio[1] = f
	this.Stdout = f
}
func (this *Cmd) SetStderr(f *os.File) {
	this.Stdio[2] = f
	this.Stderr = f
}

func (this *Cmd) Clone() (*Cmd, error) {
	rv := new(Cmd)
	rv.Args = this.Args
	rv.RawArgs = this.RawArgs
	rv.Stdio[0] = this.Stdio[0]
	rv.Stdio[1] = this.Stdio[1]
	rv.Stdio[2] = this.Stdio[2]
	rv.Stdin = this.Stdin
	rv.Stdout = this.Stdout
	rv.Stderr = this.Stderr
	rv.HookCount = this.HookCount
	rv.Tag = this.Tag
	rv.PipeSeq = this.PipeSeq
	rv.Closers = nil
	rv.OnFork = this.OnFork
	rv.OffFork = this.OffFork
	return rv, nil
}

type ArgsHookT func(it *Cmd, args []string) ([]string, error)

var argsHook = func(it *Cmd, args []string) ([]string, error) {
	return args, nil
}

func SetArgsHook(argsHook_ ArgsHookT) (rv ArgsHookT) {
	rv, argsHook = argsHook, argsHook_
	return
}

type HookT func(context.Context, *Cmd) (int, bool, error)

var hook = func(context.Context, *Cmd) (int, bool, error) {
	return 0, false, nil
}

func SetHook(hook_ HookT) (rv HookT) {
	rv, hook = hook, hook_
	return
}

var OnCommandNotFound = func(this *Cmd, err error) error {
	err = &CommandNotFound{this.Args[0], err}
	return err
}

var LastErrorLevel int

func nvl(a *os.File, b *os.File) *os.File {
	if a != nil {
		return a
	} else {
		return b
	}
}

func makeCmdline(args, rawargs []string) string {
	buffer := make([]byte, 0, 1024)
	for i, s := range args {
		if i > 0 {
			buffer = append(buffer, ' ')
		}
		if (len(rawargs) > i && len(rawargs[i]) > 0 && rawargs[i][0] == '"') || strings.ContainsAny(s, " &|<>\t\"") {
			buffer = append(buffer, '"')
			qs := strings.Replace(s, `"`, `\"`, -1)
			buffer = append(buffer, qs...)
			buffer = append(buffer, '"')
		} else {
			buffer = append(buffer, s...)
		}
	}
	return string(buffer)
}

func (this *Cmd) spawnvp_noerrmsg(ctx context.Context) (int, error) {
	// command is empty.
	if len(this.Args) <= 0 {
		return 0, nil
	}
	if DBG {
		print("spawnvp_noerrmsg('", this.Args[0], "')\n")
	}

	// aliases and lua-commands
	if errorlevel, done, err := hook(ctx, this); done || err != nil {
		return errorlevel, err
	}

	// command not found hook
	var err error
	this.Path = dos.LookPath(this.Args[0], "NYAGOSPATH")
	if this.Path == "" {
		return 255, OnCommandNotFound(this, os.ErrNotExist)
	}
	this.Args[0] = this.Path
	if DBG {
		print("exec.LookPath(", this.Args[0], ")==", this.Path, "\n")
	}

	if WildCardExpansionAlways {
		this.Args = findfile.Globs(this.Args)
	}

	// executable-file
	if FLAG_AMP2NEWCONSOLE {
		if this.SysProcAttr != nil && (this.SysProcAttr.CreationFlags&CREATE_NEW_CONSOLE) != 0 {
			err = this.Start()
			return 0, err
		}
	}

	if this.SysProcAttr == nil {
		this.SysProcAttr = new(syscall.SysProcAttr)
	}
	cmdline := makeCmdline(this.Args, this.RawArgs)
	if DBG {
		println(cmdline)
	}
	this.SysProcAttr.CmdLine = cmdline
	err = this.Run()

	errorlevel, errorlevelOk := dos.GetErrorLevel(&this.Cmd)
	if errorlevelOk {
		return errorlevel, err
	} else {
		return 255, err
	}
}

type AlreadyReportedError struct {
	Err error
}

func (this AlreadyReportedError) Error() string {
	return ""
}

func IsAlreadyReported(err error) bool {
	_, ok := err.(AlreadyReportedError)
	return ok
}

func (this *Cmd) Spawnvp() (int, error) {
	return this.SpawnvpContext(context.Background())
}

func (this *Cmd) SpawnvpContext(ctx context.Context) (int, error) {
	errorlevel, err := this.spawnvp_noerrmsg(ctx)
	if err != nil && err != io.EOF && !IsAlreadyReported(err) {
		if DBG {
			val := reflect.ValueOf(err)
			fmt.Fprintf(this.Stderr, "error-type=%s\n", val.Type())
		}
		fmt.Fprintln(this.Stderr, err.Error())
		err = AlreadyReportedError{err}
	}
	return errorlevel, err
}

var pipeSeq uint = 0

func (this *Cmd) Interpret(text string) (int, error) {
	return this.InterpretContext(context.Background(), text)
}

type gotoEol struct{}

var GotoEol = gotoEol{}

func (this *Cmd) InterpretContext(ctx_ context.Context, text string) (errorlevel int, finalerr error) {
	if DBG {
		print("Interpret('", text, "')\n")
	}
	if this == nil {
		return 255, errors.New("Fatal Error: Interpret: instance is nil")
	}
	errorlevel = 0
	finalerr = nil

	statements, statementsErr := Parse(text)
	if statementsErr != nil {
		if DBG {
			print("Parse Error:", statementsErr.Error(), "\n")
		}
		return 0, statementsErr
	}
	if argsHook != nil {
		if DBG {
			print("call argsHook\n")
		}
		for _, pipeline := range statements {
			for _, state := range pipeline {
				var err error
				state.Args, err = argsHook(this, state.Args)
				if err != nil {
					return 255, err
				}
			}
		}
		if DBG {
			print("done argsHook\n")
		}
	}
	for _, pipeline := range statements {
		for i, state := range pipeline {
			if state.Term == "|" && (i+1 >= len(pipeline) || len(pipeline[i+1].Args) <= 0) {
				return 255, errors.New("The syntax of the command is incorrect.")
			}
		}
	}

	for _, pipeline := range statements {

		var pipeIn *os.File = nil
		pipeSeq++
		isBackGround := this.IsBackGround
		for _, state := range pipeline {
			if state.Term == "&" {
				isBackGround = true
				break
			}
		}
		var wg sync.WaitGroup
		shutdown_immediately := false
		for i, state := range pipeline {
			if DBG {
				print(i, ": pipeline loop(", state.Args[0], ")\n")
			}
			cmd, err := this.Clone()
			if err != nil {
				return 255, err
			}
			cmd.PipeSeq[0] = pipeSeq
			cmd.PipeSeq[1] = uint(1 + i)
			cmd.IsBackGround = isBackGround

			ctx := context.WithValue(ctx_, GotoEol, func() {
				shutdown_immediately = true
				gotoeol, ok := ctx_.Value(GotoEol).(func())
				if ok {
					gotoeol()
				}
			})

			if pipeIn != nil {
				cmd.SetStdin(pipeIn)
				cmd.Closers = append(cmd.Closers, pipeIn)
				pipeIn = nil
			}

			if state.Term[0] == '|' {
				var pipeOut *os.File
				pipeIn, pipeOut, err = os.Pipe()
				cmd.SetStdout(pipeOut)
				if state.Term == "|&" {
					cmd.SetStderr(pipeOut)
				}
				cmd.Closers = append(cmd.Closers, pipeOut)
			}

			for _, red := range state.Redirect {
				var fd *os.File
				fd, err = red.OpenOn(cmd)
				if err != nil {
					return 0, err
				}
				defer fd.Close()
			}

			cmd.Args = state.Args
			cmd.RawArgs = state.RawArgs
			if i > 0 {
				cmd.IsBackGround = true
			}
			if i == len(pipeline)-1 && state.Term != "&" {
				// foreground execution.
				errorlevel, finalerr = cmd.SpawnvpContext(ctx)
				LastErrorLevel = errorlevel
				cmd.Close()
			} else {
				// background
				if !isBackGround {
					wg.Add(1)
				}
				if cmd.OnFork != nil {
					if err := cmd.OnFork(cmd); err != nil {
						fmt.Fprintln(cmd.Stderr, err.Error())
						return -1, err
					}
				}
				go func(cmd1 *Cmd) {
					if isBackGround {
						if FLAG_AMP2NEWCONSOLE {
							if len(pipeline) == 1 {
								cmd1.SysProcAttr = &syscall.SysProcAttr{
									CreationFlags: CREATE_NEW_CONSOLE |
										CREATE_NEW_PROCESS_GROUP,
								}
							}
						}
					} else {
						defer wg.Done()
					}
					cmd1.SpawnvpContext(ctx)
					if cmd1.OffFork != nil {
						if err := cmd1.OffFork(cmd1); err != nil {
							fmt.Fprintln(cmd1.Stderr, err.Error())
							goto exit
						}
					}
				exit:
					cmd1.Close()
				}(cmd)
			}
		}
		if !isBackGround {
			wg.Wait()
			if shutdown_immediately {
				return errorlevel, nil
			}
			if len(pipeline) > 0 {
				switch pipeline[len(pipeline)-1].Term {
				case "&&":
					if errorlevel != 0 {
						return errorlevel, nil
					}
				case "||":
					if errorlevel == 0 {
						return errorlevel, nil
					}
				}
			}
		}
	}
	return
}
<commit_msg>Remove the field exec.Cmd from shell.Cmd<commit_after>package shell

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"reflect"
	"strings"
	"sync"
	"syscall"

	"github.com/zetamatta/go-findfile"

	"../dos"
	. "../ifdbg"
)

const FLAG_AMP2NEWCONSOLE = false

var WildCardExpansionAlways = false

type CommandNotFound struct {
	Name string
	Err  error
}

// from "TDM-GCC-64/x86_64-w64-mingw32/include/winbase.h"
const (
	CREATE_NEW_CONSOLE       = 0x10
	CREATE_NEW_PROCESS_GROUP = 0x200
)

func (this CommandNotFound) Stringer() string {
	return fmt.Sprintf("'%s' is not recognized as an internal or external command,\noperable program or batch file", this.Name)
}

func (this CommandNotFound) Error() string {
	return this.Stringer()
}

type Cmd struct {
	Stdio        [3]*os.File
	Stdout       io.Writer
	Stderr       io.Writer
	Stdin        io.Reader
	Args         []string
	HookCount    int
	Tag          interface{}
	PipeSeq      [2]uint
	IsBackGround bool
	RawArgs      []string

	OnFork  func(*Cmd) error
	OffFork func(*Cmd) error
	Closers []io.Closer
}

func (this *Cmd) GetRawArgs() []string {
	return this.RawArgs
}

func (this *Cmd) Close() {
	if this.Closers != nil {
		for _, c := range this.Closers {
			c.Close()
		}
		this.Closers = nil
	}
}

func New() *Cmd {
	this := Cmd{
		Stdio: [3]*os.File{os.Stdin, os.Stdout, os.Stderr},
	}
	this.Stdin = os.Stdin
	this.Stdout = os.Stdout
	this.Stderr = os.Stderr
	this.PipeSeq[0] = pipeSeq
	this.PipeSeq[1] = 0
	return &this
}

func (this *Cmd) SetStdin(f *os.File) {
	this.Stdio[0] = f
	this.Stdin = f
}
func (this *Cmd) SetStdout(f *os.File) {
	this.Stdio[1] = f
	this.Stdout = f
}
func (this *Cmd) SetStderr(f *os.File) {
	this.Stdio[2] = f
	this.Stderr = f
}

func (this *Cmd) Clone() (*Cmd, error) {
	rv := new(Cmd)
	rv.Args = this.Args
	rv.RawArgs = this.RawArgs
	rv.Stdio[0] = this.Stdio[0]
	rv.Stdio[1] = this.Stdio[1]
	rv.Stdio[2] = this.Stdio[2]
	rv.Stdin = this.Stdin
	rv.Stdout = this.Stdout
	rv.Stderr = this.Stderr
	rv.HookCount = this.HookCount
	rv.Tag = this.Tag
	rv.PipeSeq = this.PipeSeq
	rv.Closers = nil
	rv.OnFork = this.OnFork
	rv.OffFork = this.OffFork
	return rv, nil
}

type ArgsHookT func(it *Cmd, args []string) ([]string, error)

var argsHook = func(it *Cmd, args []string) ([]string, error) {
	return args, nil
}

func SetArgsHook(argsHook_ ArgsHookT) (rv ArgsHookT) {
	rv, argsHook = argsHook, argsHook_
	return
}

type HookT func(context.Context, *Cmd) (int, bool, error)

var hook = func(context.Context, *Cmd) (int, bool, error) {
	return 0, false, nil
}

func SetHook(hook_ HookT) (rv HookT) {
	rv, hook = hook, hook_
	return
}

var OnCommandNotFound = func(this *Cmd, err error) error {
	err = &CommandNotFound{this.Args[0], err}
	return err
}

var LastErrorLevel int

func nvl(a *os.File, b *os.File) *os.File {
	if a != nil {
		return a
	} else {
		return b
	}
}

func makeCmdline(args, rawargs []string) string {
	buffer := make([]byte, 0, 1024)
	for i, s := range args {
		if i > 0 {
			buffer = append(buffer, ' ')
		}
		if (len(rawargs) > i && len(rawargs[i]) > 0 && rawargs[i][0] == '"') || strings.ContainsAny(s, " &|<>\t\"") {
			buffer = append(buffer, '"')
			qs := strings.Replace(s, `"`, `\"`, -1)
			buffer = append(buffer, qs...)
			buffer = append(buffer, '"')
		} else {
			buffer = append(buffer, s...)
		}
	}
	return string(buffer)
}

func (this *Cmd) spawnvp_noerrmsg(ctx context.Context) (int, error) {
	// command is empty.
	if len(this.Args) <= 0 {
		return 0, nil
	}
	if DBG {
		print("spawnvp_noerrmsg('", this.Args[0], "')\n")
	}

	// aliases and lua-commands
	if errorlevel, done, err := hook(ctx, this); done || err != nil {
		return errorlevel, err
	}

	// command not found hook
	var err error
	path1 := dos.LookPath(this.Args[0], "NYAGOSPATH")
	if path1 == "" {
		return 255, OnCommandNotFound(this, os.ErrNotExist)
	}
	this.Args[0] = path1

	if DBG {
		print("exec.LookPath(", this.Args[0], ")==", path1, "\n")
	}

	if WildCardExpansionAlways {
		this.Args = findfile.Globs(this.Args)
	}

	cmd1 := exec.Command(this.Args[0], this.Args[1:]...)
	cmd1.Stdin = this.Stdio[0]
	cmd1.Stdout = this.Stdio[1]
	cmd1.Stderr = this.Stdio[2]

	// executable-file
	if FLAG_AMP2NEWCONSOLE {
		if cmd1.SysProcAttr != nil && (cmd1.SysProcAttr.CreationFlags&CREATE_NEW_CONSOLE) != 0 {
			err = cmd1.Start()
			return 0, err
		}
	}

	if cmd1.SysProcAttr == nil {
		cmd1.SysProcAttr = new(syscall.SysProcAttr)
	}
	cmdline := makeCmdline(cmd1.Args, this.RawArgs)
	if DBG {
		println(cmdline)
	}
	cmd1.SysProcAttr.CmdLine = cmdline
	err = cmd1.Run()

	errorlevel, errorlevelOk := dos.GetErrorLevel(cmd1)
	if errorlevelOk {
		return errorlevel, err
	} else {
		return 255, err
	}
}

type AlreadyReportedError struct {
	Err error
}

func (this AlreadyReportedError) Error() string {
	return ""
}

func IsAlreadyReported(err error) bool {
	_, ok := err.(AlreadyReportedError)
	return ok
}

func (this *Cmd) Spawnvp() (int, error) {
	return this.SpawnvpContext(context.Background())
}

func (this *Cmd) SpawnvpContext(ctx context.Context) (int, error) {
	errorlevel, err := this.spawnvp_noerrmsg(ctx)
	if err != nil && err != io.EOF && !IsAlreadyReported(err) {
		if DBG {
			val := reflect.ValueOf(err)
			fmt.Fprintf(this.Stderr, "error-type=%s\n", val.Type())
		}
		fmt.Fprintln(this.Stderr, err.Error())
		err = AlreadyReportedError{err}
	}
	return errorlevel, err
}

var pipeSeq uint = 0

func (this *Cmd) Interpret(text string) (int, error) {
	return this.InterpretContext(context.Background(), text)
}

type gotoEol struct{}

var GotoEol = gotoEol{}

func (this *Cmd) InterpretContext(ctx_ context.Context, text string) (errorlevel int, finalerr error) {
	if DBG {
		print("Interpret('", text, "')\n")
	}
	if this == nil {
		return 255, errors.New("Fatal Error: Interpret: instance is nil")
	}
	errorlevel = 0
	finalerr = nil

	statements, statementsErr := Parse(text)
	if statementsErr != nil {
		if DBG {
			print("Parse Error:", statementsErr.Error(), "\n")
		}
		return 0, statementsErr
	}
	if argsHook != nil {
		if DBG {
			print("call argsHook\n")
		}
		for _, pipeline := range statements {
			for _, state := range pipeline {
				var err error
				state.Args, err = argsHook(this, state.Args)
				if err != nil {
					return 255, err
				}
			}
		}
		if DBG {
			print("done argsHook\n")
		}
	}
	for _, pipeline := range statements {
		for i, state := range pipeline {
			if state.Term == "|" && (i+1 >= len(pipeline) || len(pipeline[i+1].Args) <= 0) {
				return 255, errors.New("The syntax of the command is incorrect.")
			}
		}
	}

	for _, pipeline := range statements {

		var pipeIn *os.File = nil
		pipeSeq++
		isBackGround := this.IsBackGround
		for _, state := range pipeline {
			if state.Term == "&" {
				isBackGround = true
				break
			}
		}
		var wg sync.WaitGroup
		shutdown_immediately := false
		for i, state := range pipeline {
			if DBG {
				print(i, ": pipeline loop(", state.Args[0], ")\n")
			}
			cmd, err := this.Clone()
			if err != nil {
				return 255, err
			}
			cmd.PipeSeq[0] = pipeSeq
			cmd.PipeSeq[1] = uint(1 + i)
			cmd.IsBackGround = isBackGround

			ctx := context.WithValue(ctx_, GotoEol, func() {
				shutdown_immediately = true
				gotoeol, ok := ctx_.Value(GotoEol).(func())
				if ok {
					gotoeol()
				}
			})

			if pipeIn != nil {
				cmd.SetStdin(pipeIn)
				cmd.Closers = append(cmd.Closers, pipeIn)
				pipeIn = nil
			}

			if state.Term[0] == '|' {
				var pipeOut *os.File
				pipeIn, pipeOut, err = os.Pipe()
				cmd.SetStdout(pipeOut)
				if state.Term == "|&" {
					cmd.SetStderr(pipeOut)
				}
				cmd.Closers = append(cmd.Closers, pipeOut)
			}

			for _, red := range state.Redirect {
				var fd *os.File
				fd, err = red.OpenOn(cmd)
				if err != nil {
					return 0, err
				}
				defer fd.Close()
			}

			cmd.Args = state.Args
			cmd.RawArgs = state.RawArgs
			if i > 0 {
				cmd.IsBackGround = true
			}
			if i == len(pipeline)-1 && state.Term != "&" {
				// foreground execution.
				errorlevel, finalerr = cmd.SpawnvpContext(ctx)
				LastErrorLevel = errorlevel
				cmd.Close()
			} else {
				// background
				if !isBackGround {
					wg.Add(1)
				}
				if cmd.OnFork != nil {
					if err := cmd.OnFork(cmd); err != nil {
						fmt.Fprintln(cmd.Stderr, err.Error())
						return -1, err
					}
				}
				go func(cmd1 *Cmd) {
					if !isBackGround {
						defer wg.Done()
					}
					cmd1.SpawnvpContext(ctx)
					if cmd1.OffFork != nil {
						if err := cmd1.OffFork(cmd1); err != nil {
							fmt.Fprintln(cmd1.Stderr, err.Error())
							goto exit
						}
					}
				exit:
					cmd1.Close()
				}(cmd)
			}
		}
		if !isBackGround {
			wg.Wait()
			if shutdown_immediately {
				return errorlevel, nil
			}
			if len(pipeline) > 0 {
				switch pipeline[len(pipeline)-1].Term {
				case "&&":
					if errorlevel != 0 {
						return errorlevel, nil
					}
				case "||":
					if errorlevel == 0 {
						return errorlevel, nil
					}
				}
			}
		}
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"github.com/brotherlogic/goserver"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pbd "github.com/brotherlogic/discovery/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
	pbs "github.com/brotherlogic/goserver/proto"
)

// Server the main server type
type Server struct {
	*goserver.GoServer
	runner *Runner
}

func getIP(name string, server string) (string, int) {
	conn, _ := grpc.Dial("192.168.86.34:50055", grpc.WithInsecure())
	defer conn.Close()

	registry := pbd.NewDiscoveryServiceClient(conn)
	entry := pbd.RegistryEntry{Name: name, Identifier: server}
	r, _ := registry.Discover(context.Background(), &entry)
	return r.Ip, int(r.Port)
}

// updateState of the runner command
func updateState(com *runnerCommand) {
	elems := strings.Split(com.details.Spec.Name, "/")
	dServer, dPort := getIP(elems[len(elems)-1], com.details.Spec.Server)
	dConn, err := grpc.Dial(dServer+":"+strconv.Itoa(dPort), grpc.WithInsecure())

	if err != nil {
		panic(err)
	}

	defer dConn.Close()
	c := pbs.NewGoserverServiceClient(dConn)
	_, err = c.IsAlive(context.Background(), &pbs.Alive{})
	com.details.Running = (err != nil)
}

// BuildJob builds out a job
func (s *Server) BuildJob(ctx context.Context, in *pb.JobSpec) (*pb.Empty, error) {
	s.runner.Checkout(in.Name)
	return &pb.Empty{}, nil
}

// List lists all running jobs
func (s *Server) List(ctx context.Context, in *pb.Empty) (*pb.JobList, error) {
	details := &pb.JobList{}
	for _, job := range s.runner.backgroundTasks {
		updateState(job)
		details.Details = append(details.Details, job.details)
	}

	return details, nil
}

// Run runs a background task
func (s *Server) Run(ctx context.Context, in *pb.JobSpec) (*pb.Empty, error) {
	s.runner.Run(in)
	return &pb.Empty{}, nil
}

// DoRegister Registers this server
func (s Server) DoRegister(server *grpc.Server) {
	pb.RegisterGoBuildSlaveServer(server, &s)
}

//Init builds the default runner framework
func Init() *Runner {
	r := &Runner{gopath: "goautobuild"}
	r.runner = runCommand
	go r.run()
	return r
}

func runCommand(c *runnerCommand) {
	log.Printf("RUNNING COMMAND: %v", c)
	env := os.Environ()
	home := ""
	for _, s := range env {
		if strings.HasPrefix(s, "HOME=") {
			home = s[5:]
		}
	}

	if len(home) == 0 {

	}
	gpath := home + "/gobuild"
	c.command.Path = strings.Replace(c.command.Path, "$GOPATH", gpath, -1)
	for i := range c.command.Args {
		c.command.Args[i] = strings.Replace(c.command.Args[i], "$GOPATH", gpath, -1)
	}

	path := fmt.Sprintf("GOPATH=" + home + "/gobuild")
	found := false
	log.Printf("HERE = %v", c.command.Env)
	envl := os.Environ()
	for i, blah := range envl {
		if strings.HasPrefix(blah, "GOPATH") {
			envl[i] = path
			found = true
		}
	}
	if !found {
		envl = append(envl, path)
	}
	log.Printf("ENV = %v", envl)
	c.command.Env = envl

	out, err := c.command.StdoutPipe()
	out2, err2 := c.command.StderrPipe()
	if err != nil {
		log.Printf("Blah: %v", err)
	}

	if err2 != nil {
		log.Printf("Blah2: %v", err)
	}

	log.Printf("%v, %v and %v", c.command.Path, c.command.Args, c.command.Env)
	c.command.Start()

	if !c.background {
		buf := new(bytes.Buffer)
		buf.ReadFrom(out)
		str := buf.String()

		buf2 := new(bytes.Buffer)
		buf2.ReadFrom(out2)
		str2 := buf2.String()
		log.Printf("%v and %v", str, str2)

		c.command.Wait()
		c.output = str
		c.complete = true
	}
	log.Printf("DONE")
}

func main() {
	s := Server{&goserver.GoServer{}, Init()}
	s.Register = s
	s.PrepServer()
	s.RegisterServer("gobuildslave", false)
	s.Serve()
}
<commit_msg>Fixes running test<commit_after>package main

import (
	"bytes"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"github.com/brotherlogic/goserver"
	"golang.org/x/net/context"
	"google.golang.org/grpc"

	pbd "github.com/brotherlogic/discovery/proto"
	pb "github.com/brotherlogic/gobuildslave/proto"
	pbs "github.com/brotherlogic/goserver/proto"
)

// Server the main server type
type Server struct {
	*goserver.GoServer
	runner *Runner
}

func getIP(name string, server string) (string, int) {
	conn, _ := grpc.Dial("192.168.86.34:50055", grpc.WithInsecure())
	defer conn.Close()

	registry := pbd.NewDiscoveryServiceClient(conn)
	entry := pbd.RegistryEntry{Name: name, Identifier: server}
	r, _ := registry.Discover(context.Background(), &entry)
	return r.Ip, int(r.Port)
}

// updateState of the runner command
func updateState(com *runnerCommand) {
	elems := strings.Split(com.details.Spec.Name, "/")
	dServer, dPort := getIP(elems[len(elems)-1], com.details.Spec.Server)
	dConn, err := grpc.Dial(dServer+":"+strconv.Itoa(dPort), grpc.WithInsecure())

	if err != nil {
		panic(err)
	}

	defer dConn.Close()
	c := pbs.NewGoserverServiceClient(dConn)
	_, err = c.IsAlive(context.Background(), &pbs.Alive{})
	com.details.Running = (err == nil)
}

// BuildJob builds out a job
func (s *Server) BuildJob(ctx context.Context, in *pb.JobSpec) (*pb.Empty, error) {
	s.runner.Checkout(in.Name)
	return &pb.Empty{}, nil
}

// List lists all running jobs
func (s *Server) List(ctx context.Context, in *pb.Empty) (*pb.JobList, error) {
	details := &pb.JobList{}
	for _, job := range s.runner.backgroundTasks {
		updateState(job)
		details.Details = append(details.Details, job.details)
	}

	return details, nil
}

// Run runs a background task
func (s *Server) Run(ctx context.Context, in *pb.JobSpec) (*pb.Empty, error) {
	s.runner.Run(in)
	return &pb.Empty{}, nil
}

// DoRegister Registers this server
func (s Server) DoRegister(server *grpc.Server) {
	pb.RegisterGoBuildSlaveServer(server, &s)
}

//Init builds the default runner framework
func Init() *Runner {
	r := &Runner{gopath: "goautobuild"}
	r.runner = runCommand
	go r.run()
	return r
}

func runCommand(c *runnerCommand) {
	log.Printf("RUNNING COMMAND: %v", c)
	env := os.Environ()
	home := ""
	for _, s := range env {
		if strings.HasPrefix(s, "HOME=") {
			home = s[5:]
		}
	}

	if len(home) == 0 {

	}
	gpath := home + "/gobuild"
	c.command.Path = strings.Replace(c.command.Path, "$GOPATH", gpath, -1)
	for i := range c.command.Args {
		c.command.Args[i] = strings.Replace(c.command.Args[i], "$GOPATH", gpath, -1)
	}

	path := fmt.Sprintf("GOPATH=" + home + "/gobuild")
	found := false
	log.Printf("HERE = %v", c.command.Env)
	envl := os.Environ()
	for i, blah := range envl {
		if strings.HasPrefix(blah, "GOPATH") {
			envl[i] = path
			found = true
		}
	}
	if !found {
		envl = append(envl, path)
	}
	log.Printf("ENV = %v", envl)
	c.command.Env = envl

	out, err := c.command.StdoutPipe()
	out2, err2 := c.command.StderrPipe()
	if err != nil {
		log.Printf("Blah: %v", err)
	}

	if err2 != nil {
		log.Printf("Blah2: %v", err)
	}

	log.Printf("%v, %v and %v", c.command.Path, c.command.Args, c.command.Env)
	c.command.Start()

	if !c.background {
		buf := new(bytes.Buffer)
		buf.ReadFrom(out)
		str := buf.String()

		buf2 := new(bytes.Buffer)
		buf2.ReadFrom(out2)
		str2 := buf2.String()
		log.Printf("%v and %v", str, str2)

		c.command.Wait()
		c.output = str
		c.complete = true
	}
	log.Printf("DONE")
}

func main() {
	s := Server{&goserver.GoServer{}, Init()}
	s.Register = s
	s.PrepServer()
	s.RegisterServer("gobuildslave", false)
	s.Serve()
}
<|endoftext|>


<commit_before>package url

import (
	"encoding/json"
	"fmt"
	"github.com/0x263b/Porygon2"
	"github.com/PuerkitoBio/goquery"
	"github.com/dustin/go-humanize"
	"github.com/kennygrant/sanitize"
	"html"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// 4chan API thread structure
// https://github.com/4chan/4chan-API
type ChanPost struct {
	Posts []struct {
		No  int    `json:"no"`
		Com string `json:"com"`
	} `json:"posts"`
}

// Reddit API thread structure
// https://www.reddit.com/dev/api
type RedditComment []struct {
	Data struct {
		Children []struct {
			Kind string `json:"kind"`
			Data struct {
				Body string `json:"body"`
			} `json:"data"`
		} `json:"children"`
	} `json:"data"`
}

// Used to parse youtube's ISO 8601 durations
// https://en.wikipedia.org/wiki/ISO_8601#Durations
func ParseDuration(str string) time.Duration {
	durationRegex := regexp.MustCompile(`P(?P<years>\d+Y)?(?P<months>\d+M)?(?P<days>\d+D)?T?(?P<hours>\d+H)?(?P<minutes>\d+M)?(?P<seconds>\d+S)?`)
	matches := durationRegex.FindStringSubmatch(str)

	years := ParseInt64(matches[1])
	months := ParseInt64(matches[2])
	days := ParseInt64(matches[3])
	hours := ParseInt64(matches[4])
	minutes := ParseInt64(matches[5])
	seconds := ParseInt64(matches[6])

	hour := int64(time.Hour)
	minute := int64(time.Minute)
	second := int64(time.Second)
	return time.Duration(years*24*365*hour + months*30*24*hour + days*24*hour + hours*hour + minutes*minute + seconds*second)
}

func ParseInt64(value string) int64 {
	if len(value) == 0 {
		return 0
	}
	parsed, err := strconv.Atoi(value[:len(value)-1])
	if err != nil {
		return 0
	}
	return int64(parsed)
}

var timeout = time.Duration(3) * time.Second

func dialTimeout(network, addr string) (net.Conn, error) {
	conn, err := net.DialTimeout(network, addr, timeout)
	return conn, err
}

func extractURL(text string) string {
	extractedURL := ""
	for _, value := range strings.Split(text, " ") {
		parsedURL, err := url.Parse(value)
		if err != nil {
			continue
		}
		if strings.HasPrefix(parsedURL.Scheme, "http") {
			extractedURL = parsedURL.String()
			break
		}
	}
	return extractedURL
}

func openGraphTitle(command *bot.PassiveCmd) (string, error) {
	if !bot.GetChannelKey(command.Channel, "urls") {
		return "", nil
	}

	URL := extractURL(command.Raw)

	if URL == "" {
		return "", nil
	}

	transport := &http.Transport{
		Dial: dialTimeout,
	}

	cookieJar, _ := cookiejar.New(nil)

	client := &http.Client{
		Jar:       cookieJar, // Some sites require cookies to show you anything (nytimes)
		Transport: transport, // Time out if connection hangs
	}

	response, err := client.Get(URL)
	if err != nil {
		return "", err
	}

	finalURL := response.Request.URL.Host

	var bytes int64 = 40960
	// Youtube's html buries the duration at the bottom
	// so we have to read the first 100kB
	if finalURL == "www.youtube.com" {
		bytes = 102400
	}

	if response.Header.Get("Content-Type") == "" {
		// Some servers don't give us anything to work with
		return fmt.Sprintf("Title | (no title) | %s", finalURL), nil
	} else if !strings.Contains(response.Header.Get("Content-Type"), "text/html") {
		if !bot.GetChannelKey(command.Channel, "files") {
			return "", nil
		}
		contentType := response.Header.Get("Content-Type")
		contentLength := response.Header.Get("Content-Length")
		size, _ := strconv.ParseUint(contentLength, 10, 64)
		return fmt.Sprintf("File | %s %s | %s", contentType, humanize.Bytes(size), finalURL), nil
	}

	defer response.Body.Close()
	body := response.Body
	chunk := io.LimitReader(body, bytes) // Download/Read first 20kB

	doc, err := goquery.NewDocumentFromReader(chunk)
	if err != nil {
		fmt.Println(err)
		return "", err
	}

	title := doc.Find("title").Text()

	// Generally <meta> tags have more useful titles
	doc.Find("meta[property='og:title']").Each(func(i int, s *goquery.Selection) {
		title = s.AttrOr("content", title)
	})

	// Get tweet content from <meta>
	if finalURL == "twitter.com" {
		doc.Find("meta[property='og:description']").Each(func(i int, s *goquery.Selection) {
			title = fmt.Sprintf("%s: %s", title, s.AttrOr("content", title))
		})
	}

	// Get video duration from <meta>
	if finalURL == "www.youtube.com" {
		doc.Find("meta[itemprop='duration']").Each(func(i int, s *goquery.Selection) {
			title = fmt.Sprintf("%s | %s", title, ParseDuration(s.AttrOr("content", title)))
		})
	}

	// Get 4chan post
	if finalURL == "boards.4chan.org" {
		if strings.Contains(response.Request.URL.Path, "/thread/") {
			path := strings.Split(response.Request.URL.Path, "/")
			postId := response.Request.URL.Fragment

			response, _ := client.Get(fmt.Sprintf("https://a.4cdn.org/%s/thread/%s.json", path[1], path[3]))

			if response.StatusCode != 200 {
				title = "404 Not Found"
			} else {

				defer response.Body.Close()
				body, _ := ioutil.ReadAll(response.Body)

				var posts ChanPost
				json.Unmarshal(body, &posts)

				title = posts.Posts[0].Com

				if postId != "" {
					for _, element := range posts.Posts {
						if strings.Contains(postId, strconv.Itoa(element.No)) {
							title = element.Com
							break
						}
					}
				}

				title = sanitize.HTML(title) // Remove any unwanted html
			}

			if len(title) < 1 {
				title = "(blank post)"
			}
		}
	}

	if finalURL == "www.reddit.com" {
		thread_title := title
		if strings.Contains(response.Request.URL.Path, "/comments/") {

			response, _ := client.Get(fmt.Sprintf("%s.json", URL))

			if response.StatusCode != 200 {
				fmt.Println(response.StatusCode)
				title = "404 Not Found"
			} else {

				defer response.Body.Close()

				body, _ := ioutil.ReadAll(response.Body)
				var comments RedditComment
				json.Unmarshal(body, &comments)

				children := comments[len(comments)-1].Data.Children

				if len(children) > 0 {
					title = children[len(children)-1].Data.Body
				}

				if len(title) < 1 {
					title = thread_title
				}
			}
		}
	}

	reg := regexp.MustCompile("\\s+")
	title = reg.ReplaceAllString(title, " ") // Strip tabs and newlines
	title = strings.TrimSpace(title)         // then trim excessive spaces

	if len(title) > 200 {
		title = fmt.Sprintf("%s …", title[0:200])
	} else if len(title) < 1 {
		title = "(no title)"
	}

	return fmt.Sprintf("Title | %s | %s", html.UnescapeString(title), finalURL), nil
}

func toggleURLs(command *bot.Cmd, matches []string) (msg string, err error) {
	if !bot.IsAdmin(command.Nick) || !bot.IsPrivateMsg(command.Channel, command.Nick) {
		return "", nil
	}

	onOff := matches[1]
	channelToToggle := matches[2]

	if onOff == "on" {
		bot.SetChannelKey(channelToToggle, "urls", true)
		return fmt.Sprintf("Reacting to URLs in %s", channelToToggle), nil
	} else if onOff == "off" {
		bot.SetChannelKey(channelToToggle, "urls", false)
		return fmt.Sprintf("No longer displaying page titles in %s", channelToToggle), nil
	}
	return "", nil
}

func toggleFiles(command *bot.Cmd, matches []string) (msg string, err error) {
	if !bot.IsAdmin(command.Nick) || !bot.IsPrivateMsg(command.Channel, command.Nick) {
		return "", nil
	}

	onOff := matches[1]
	channelToToggle := matches[2]

	if onOff == "on" {
		bot.SetChannelKey(channelToToggle, "files", true)
		return fmt.Sprintf("Reacting to file URLs in %s", channelToToggle), nil
	} else if onOff == "off" {
		bot.SetChannelKey(channelToToggle, "files", false)
		return fmt.Sprintf("No longer displaying file info in %s", channelToToggle), nil
	}
	return "", nil
}

func init() {
	bot.RegisterPassiveCommand(
		"opengraph",
		openGraphTitle)

	bot.RegisterCommand(
		"^set urls (\\S+) (\\S+)$",
		toggleURLs)

	bot.RegisterCommand(
		"^set files (\\S+) (\\S+)$",
		toggleFiles)
}
<commit_msg>Reddit comments<commit_after>package url

import (
	"encoding/json"
	"fmt"
	"github.com/0x263b/Porygon2"
	"github.com/PuerkitoBio/goquery"
	"github.com/dustin/go-humanize"
	"github.com/kennygrant/sanitize"
	"html"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// 4chan API thread structure
// https://github.com/4chan/4chan-API
type ChanPost struct {
	Posts []struct {
		No  int    `json:"no"`
		Com string `json:"com"`
	} `json:"posts"`
}

// Reddit API thread structure
// https://www.reddit.com/dev/api
type RedditComment []struct {
	Data struct {
		Children []struct {
			Kind string `json:"kind"`
			Data struct {
				Body string `json:"body"`
			} `json:"data"`
		} `json:"children"`
	} `json:"data"`
}

// Used to parse youtube's ISO 8601 durations
// https://en.wikipedia.org/wiki/ISO_8601#Durations
func ParseDuration(str string) time.Duration {
	durationRegex := regexp.MustCompile(`P(?P<years>\d+Y)?(?P<months>\d+M)?(?P<days>\d+D)?T?(?P<hours>\d+H)?(?P<minutes>\d+M)?(?P<seconds>\d+S)?`)
	matches := durationRegex.FindStringSubmatch(str)

	years := ParseInt64(matches[1])
	months := ParseInt64(matches[2])
	days := ParseInt64(matches[3])
	hours := ParseInt64(matches[4])
	minutes := ParseInt64(matches[5])
	seconds := ParseInt64(matches[6])

	hour := int64(time.Hour)
	minute := int64(time.Minute)
	second := int64(time.Second)
	return time.Duration(years*24*365*hour + months*30*24*hour + days*24*hour + hours*hour + minutes*minute + seconds*second)
}

func ParseInt64(value string) int64 {
	if len(value) == 0 {
		return 0
	}
	parsed, err := strconv.Atoi(value[:len(value)-1])
	if err != nil {
		return 0
	}
	return int64(parsed)
}

var timeout = time.Duration(3) * time.Second

func dialTimeout(network, addr string) (net.Conn, error) {
	conn, err := net.DialTimeout(network, addr, timeout)
	return conn, err
}

func extractURL(text string) string {
	extractedURL := ""
	for _, value := range strings.Split(text, " ") {
		parsedURL, err := url.Parse(value)
		if err != nil {
			continue
		}
		if strings.HasPrefix(parsedURL.Scheme, "http") {
			extractedURL = parsedURL.String()
			break
		}
	}
	return extractedURL
}

func openGraphTitle(command *bot.PassiveCmd) (string, error) {
	if !bot.GetChannelKey(command.Channel, "urls") {
		return "", nil
	}

	URL := extractURL(command.Raw)

	if URL == "" {
		return "", nil
	}

	transport := &http.Transport{
		Dial: dialTimeout,
	}

	cookieJar, _ := cookiejar.New(nil)

	client := &http.Client{
		Jar:       cookieJar, // Some sites require cookies to show you anything (nytimes)
		Transport: transport, // Time out if connection hangs
	}

	response, err := client.Get(URL)
	if err != nil {
		return "", err
	}

	finalURL := response.Request.URL.Host

	var bytes int64 = 40960
	// Youtube's html buries the duration at the bottom
	// so we have to read the first 100kB
	if finalURL == "www.youtube.com" {
		bytes = 102400
	}

	if response.Header.Get("Content-Type") == "" {
		// Some servers don't give us anything to work with
		return fmt.Sprintf("Title | (no title) | %s", finalURL), nil
	} else if !strings.Contains(response.Header.Get("Content-Type"), "text/html") {
		if !bot.GetChannelKey(command.Channel, "files") {
			return "", nil
		}
		contentType := response.Header.Get("Content-Type")
		contentLength := response.Header.Get("Content-Length")
		size, _ := strconv.ParseUint(contentLength, 10, 64)
		return fmt.Sprintf("File | %s %s | %s", contentType, humanize.Bytes(size), finalURL), nil
	}

	defer response.Body.Close()
	body := response.Body
	chunk := io.LimitReader(body, bytes) // Download/Read first 20kB

	doc, err := goquery.NewDocumentFromReader(chunk)
	if err != nil {
		fmt.Println(err)
		return "", err
	}

	title := doc.Find("title").Text()

	// Generally <meta> tags have more useful titles
	doc.Find("meta[property='og:title']").Each(func(i int, s *goquery.Selection) {
		title = s.AttrOr("content", title)
	})

	// Get tweet content from <meta>
	if finalURL == "twitter.com" {
		doc.Find("meta[property='og:description']").Each(func(i int, s *goquery.Selection) {
			title = fmt.Sprintf("%s: %s", title, s.AttrOr("content", title))
		})
	}

	// Get video duration from <meta>
	if finalURL == "www.youtube.com" {
		doc.Find("meta[itemprop='duration']").Each(func(i int, s *goquery.Selection) {
			title = fmt.Sprintf("%s | %s", title, ParseDuration(s.AttrOr("content", title)))
		})
	}

	// Get 4chan post
	if finalURL == "boards.4chan.org" {
		if strings.Contains(response.Request.URL.Path, "/thread/") {
			path := strings.Split(response.Request.URL.Path, "/")
			postId := response.Request.URL.Fragment

			response, _ := client.Get(fmt.Sprintf("https://a.4cdn.org/%s/thread/%s.json", path[1], path[3]))

			if response.StatusCode != 200 {
				title = "404 Not Found"
			} else {

				defer response.Body.Close()
				body, _ := ioutil.ReadAll(response.Body)

				var posts ChanPost
				json.Unmarshal(body, &posts)

				title = posts.Posts[0].Com

				if postId != "" {
					for _, element := range posts.Posts {
						if strings.Contains(postId, strconv.Itoa(element.No)) {
							title = element.Com
							break
						}
					}
				}

				title = sanitize.HTML(title) // Remove any unwanted html
			}

			if len(title) < 1 {
				title = "(blank post)"
			}
		}
	}

	if finalURL == "www.reddit.com" {
		thread_title := title
		r := regexp.MustCompile(`\/r\/\w+\/comments\/\w+\/\w+\/\w+`)

		if r.MatchString(response.Request.URL.Path) {

			response, _ := client.Get(fmt.Sprintf("%s.json", URL))

			if response.StatusCode != 200 {
				fmt.Println(response.StatusCode)
				title = "404 Not Found"
			} else {

				defer response.Body.Close()

				body, _ := ioutil.ReadAll(response.Body)
				var comments RedditComment
				json.Unmarshal(body, &comments)

				children := comments[len(comments)-1].Data.Children

				if len(children) > 0 {
					title = children[len(children)-1].Data.Body
				}

				if len(title) < 1 {
					title = thread_title
				}
			}
		}
	}

	reg := regexp.MustCompile("\\s+")
	title = reg.ReplaceAllString(title, " ") // Strip tabs and newlines
	title = strings.TrimSpace(title)         // then trim excessive spaces

	if len(title) > 200 {
		title = fmt.Sprintf("%s …", title[0:200])
	} else if len(title) < 1 {
		title = "(no title)"
	}

	return fmt.Sprintf("Title | %s | %s", html.UnescapeString(title), finalURL), nil
}

func toggleURLs(command *bot.Cmd, matches []string) (msg string, err error) {
	if !bot.IsAdmin(command.Nick) || !bot.IsPrivateMsg(command.Channel, command.Nick) {
		return "", nil
	}

	onOff := matches[1]
	channelToToggle := matches[2]

	if onOff == "on" {
		bot.SetChannelKey(channelToToggle, "urls", true)
		return fmt.Sprintf("Reacting to URLs in %s", channelToToggle), nil
	} else if onOff == "off" {
		bot.SetChannelKey(channelToToggle, "urls", false)
		return fmt.Sprintf("No longer displaying page titles in %s", channelToToggle), nil
	}
	return "", nil
}

func toggleFiles(command *bot.Cmd, matches []string) (msg string, err error) {
	if !bot.IsAdmin(command.Nick) || !bot.IsPrivateMsg(command.Channel, command.Nick) {
		return "", nil
	}

	onOff := matches[1]
	channelToToggle := matches[2]

	if onOff == "on" {
		bot.SetChannelKey(channelToToggle, "files", true)
		return fmt.Sprintf("Reacting to file URLs in %s", channelToToggle), nil
	} else if onOff == "off" {
		bot.SetChannelKey(channelToToggle, "files", false)
		return fmt.Sprintf("No longer displaying file info in %s", channelToToggle), nil
	}
	return "", nil
}

func init() {
	bot.RegisterPassiveCommand(
		"opengraph",
		openGraphTitle)

	bot.RegisterCommand(
		"^set urls (\\S+) (\\S+)$",
		toggleURLs)

	bot.RegisterCommand(
		"^set files (\\S+) (\\S+)$",
		toggleFiles)
}
<|endoftext|>


<commit_before>package lifted

func (n *LeafNode) dnf() Formula { return n }

func (n *AndNode) dnf() Formula {
	n.Left = n.Left.dnf()
	n.Right = n.Right.dnf()

	var disj *OrNode
	var other Formula
	if l, ok := n.Left.(*OrNode); ok {
		disj = l
		other = n.Right
	} else if r, ok := n.Right.(*OrNode); ok {
		disj = r
		other = n.Left
	}

	if disj == nil {
		return n
	}

	left := Conjunct(other, disj.Left)
	right := Conjunct(other, disj.Right)
	return Disjunct(left.dnf(), right.dnf()).dnf()
}

func (n *OrNode) dnf() Formula {
	n.Left = n.Left.dnf()
	n.Right = n.Right.dnf()
	return n
}

func (n *NotNode) dnf() Formula {
	switch f := n.Formula.dnf().(type) {
	case *OrNode:
		return Conjunct(Negate(f.Left), Negate(f.Right)).dnf()
	case *AndNode:
		return Disjunct(Negate(f.Left), Negate(f.Right)).dnf()
	}
	return Negate(n.Formula)
}

func (n *QuantNode) dnf() Formula {
	panic("QuantNode in the tree when converting to DFN")
}

func (n *WhenNode) dnf() Formula {
	n.Formula = n.Formula.dnf()

	disj := Formula(MakeFalse())
	conds := collectOrs(n.Condition.dnf())
	if len(conds) == 1 {
		return n
	}
	for _, cond := range conds {
		nd := &WhenNode{
			Condition: cond,
			UnaryNode: n.UnaryNode,
		}
		disj = Disjunct(disj, Formula(nd))
	}
	return disj
}

func (*LeafNode) ensureDnf() { return }

func (n *AndNode) ensureDnf() {
	switch n.Left.(type) {
	case *OrNode:
		panic("An OrNode follows an AndNode in a 'DNF' formula")
	default:
		n.Left.ensureDnf()
	}
	switch n.Right.(type) {
	case *OrNode:
		panic("OrNode found beneath an AndNode in a 'DNF' formula")
	default:
		n.Right.ensureDnf()
	}
}

func (*NotNode) ensureDnf() {
	panic("NotNode found in a 'DNF' formula")
}

func (n *QuantNode) ensureDnf() {
	panic("QuantNode in the tree when converting to DFN")
}

func (n *OrNode) ensureDnf() {
	n.Left.ensureDnf()
	n.Right.ensureDnf()
}

func (n *WhenNode) ensureDnf() {
	switch n.Condition.(type) {
	case *OrNode:
		panic("OrNode found beneath a WhenNode in a 'DNF' formula")
	default:
		n.Condition.ensureDnf()
	}
	switch n.Formula.(type) {
	case *OrNode:
		panic("OrNode found beneath a WhenNode in a 'DNF' formula")
	default:
		n.Formula.ensureDnf()
	}
}

func collectOrs(f Formula) (fs []Formula) {
	switch n := f.(type) {
	case *OrNode:
		fs = append(fs, collectOrs(n.Left)...)
		fs = append(fs, collectOrs(n.Right)...)
	default:
		fs = append(fs, n)
	}
	return
}
<commit_msg>Simplyfy ensureDnf() by using type assertions instead of type switches.<commit_after>package lifted

func (n *LeafNode) dnf() Formula { return n }

func (n *AndNode) dnf() Formula {
	n.Left = n.Left.dnf()
	n.Right = n.Right.dnf()

	var disj *OrNode
	var other Formula
	if l, ok := n.Left.(*OrNode); ok {
		disj = l
		other = n.Right
	} else if r, ok := n.Right.(*OrNode); ok {
		disj = r
		other = n.Left
	}

	if disj == nil {
		return n
	}

	left := Conjunct(other, disj.Left)
	right := Conjunct(other, disj.Right)
	return Disjunct(left.dnf(), right.dnf()).dnf()
}

func (n *OrNode) dnf() Formula {
	n.Left = n.Left.dnf()
	n.Right = n.Right.dnf()
	return n
}

func (n *NotNode) dnf() Formula {
	switch f := n.Formula.dnf().(type) {
	case *OrNode:
		return Conjunct(Negate(f.Left), Negate(f.Right)).dnf()
	case *AndNode:
		return Disjunct(Negate(f.Left), Negate(f.Right)).dnf()
	}
	return Negate(n.Formula)
}

func (n *QuantNode) dnf() Formula {
	panic("QuantNode in the tree when converting to DFN")
}

func (n *WhenNode) dnf() Formula {
	n.Formula = n.Formula.dnf()

	disj := Formula(MakeFalse())
	conds := collectOrs(n.Condition.dnf())
	if len(conds) == 1 {
		return n
	}
	for _, cond := range conds {
		nd := &WhenNode{
			Condition: cond,
			UnaryNode: n.UnaryNode,
		}
		disj = Disjunct(disj, Formula(nd))
	}
	return disj
}

func (*LeafNode) ensureDnf() { return }

func (n *AndNode) ensureDnf() {
	if _, ok := n.Left.(*OrNode); ok {
		panic("An OrNode follows an AndNode in a 'DNF' formula")
	}
	n.Left.ensureDnf()
	if _, ok := n.Right.(*OrNode); ok {
		panic("OrNode found beneath an AndNode in a 'DNF' formula")
	}
	n.Right.ensureDnf()
}

func (*NotNode) ensureDnf() {
	panic("NotNode found in a 'DNF' formula")
}

func (n *QuantNode) ensureDnf() {
	panic("QuantNode in the tree when converting to DFN")
}

func (n *OrNode) ensureDnf() {
	n.Left.ensureDnf()
	n.Right.ensureDnf()
}

func (n *WhenNode) ensureDnf() {
	if _, ok := n.Condition.(*OrNode); ok {
		panic("OrNode found beneath a WhenNode in a 'DNF' formula")
	}
	n.Condition.ensureDnf()
	if _, ok := n.Formula.(*OrNode); ok {
		panic("OrNode found beneath a WhenNode in a 'DNF' formula")
	}
	n.Formula.ensureDnf()
}

func collectOrs(f Formula) (fs []Formula) {
	switch n := f.(type) {
	case *OrNode:
		fs = append(fs, collectOrs(n.Left)...)
		fs = append(fs, collectOrs(n.Right)...)
	default:
		fs = append(fs, n)
	}
	return
}
<|endoftext|>


<commit_before>package guid

import (
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/mh-cbon/go-msi/util"
)

func Make() (string, error) {
	if runtime.GOOS == "windows" {

		b, err := util.GetBinPath()
		if err != nil {
			return "", err
		}

		cmd := "cscript.exe"
		args := []string{filepath.Join(filepath.Base(b), "utils", "myuuid.vbs")}
		out, err := exec.Command(cmd, args...).CombinedOutput()
		if err != nil {
			return "", err
		}
		sout := string(out)
		sout = strings.TrimSpace(sout)
		sout = strings.ToUpper(sout)
		return sout, nil
	} else {
		cmd := "uuidgen"
		args := []string{"-r"}
		out, err := exec.Command(cmd, args...).CombinedOutput()
		if err != nil {
			return "", err
		}
		sout := string(out)
		sout = strings.TrimSpace(sout)
		sout = strings.ToUpper(sout)
		return sout, nil
	}
}
<commit_msg>doc and lint<commit_after>package guid

import (
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/mh-cbon/go-msi/util"
)

// Make generates an uuid compatible with msi requirements.
func Make() (string, error) {

	if runtime.GOOS == "windows" {
		b, err := util.GetBinPath()
		if err != nil {
			return "", err
		}

		cmd := "cscript.exe"
		args := []string{filepath.Join(filepath.Base(b), "utils", "myuuid.vbs")}
		out, err := exec.Command(cmd, args...).CombinedOutput()
		if err != nil {
			return "", err
		}
		sout := string(out)
		sout = strings.TrimSpace(sout)
		sout = strings.ToUpper(sout)
		return sout, nil
	}

	cmd := "uuidgen"
	args := []string{"-r"}
	out, err := exec.Command(cmd, args...).CombinedOutput()
	if err != nil {
		return "", err
	}
	sout := string(out)
	sout = strings.TrimSpace(sout)
	sout = strings.ToUpper(sout)
	return sout, nil
}
<|endoftext|>