<commit_before>// Copyright 2017-2019, Square, Inc.

// Package server bootstraps and runs the Request Manager.
package server

import (
	"encoding/json"
	"fmt"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/square/spincycle/v2/config"
	"github.com/square/spincycle/v2/jobs"
	"github.com/square/spincycle/v2/request-manager/api"
	"github.com/square/spincycle/v2/request-manager/app"
	"github.com/square/spincycle/v2/request-manager/auth"
	"github.com/square/spincycle/v2/request-manager/graph"
	"github.com/square/spincycle/v2/request-manager/joblog"
	"github.com/square/spincycle/v2/request-manager/request"
	"github.com/square/spincycle/v2/request-manager/spec"
	"github.com/square/spincycle/v2/request-manager/status"
)

var (
	// How often the request resumer is run.
	ResumerInterval = 10 * time.Second

	// How long Suspended Job Chains have to be resumed before they're deleted.
	SJCTTL = 1 * time.Hour
)

type Server struct {
	appCtx app.Context
	api    *api.API

	shutdownChan   chan struct{}
	resumerStopped chan struct{}
	apiStopped     chan struct{}
	stopped        bool
	stopMux        sync.Mutex
}

func NewServer(appCtx app.Context) *Server {
	return &Server{
		appCtx:         appCtx,
		resumerStopped: make(chan struct{}),
		apiStopped:     make(chan struct{}),
		shutdownChan:   make(chan struct{}),
		stopMux:        sync.Mutex{},
	}
}

// Run runs the Request Manager API and Request Resumer. It returns when the API
// stops running (either from an error, or after a call to Stop). If a custom RunAPI
// hook has been provided, it will be called to run the API instead of the default
// api.Run.
//
// If stopOnSignal = true, the server will listen for TERM and INT signals from the
// OS and call Stop to shut itself down when those signals are received. Else, the
// caller must call Stop to shut down the server.
func (s *Server) Run(stopOnSignal bool) error {
	if s.api == nil {
		panic("Server.Run called before Server.Boot")
	}
	if s.stopped {
		return fmt.Errorf("server stopped")
	}

	// Run the request resumer in a goroutine, so we can block on running the api.
	go func() {
		defer close(s.resumerStopped) // indicate the resumer is done running

		// Every 10 seconds until the server is stopped, resume all Suspended Job
		// Chains and clean up any that are in a bad state.
		ticker := time.NewTicker(ResumerInterval)
	RESUMER:
		for {
			select {
			case <-s.shutdownChan:
				break RESUMER
			case <-ticker.C:
				s.appCtx.RR.ResumeAll()
				s.appCtx.RR.Cleanup()
			}
		}
		ticker.Stop()
	}()

	// If stopOnSignal = true, watch for TERM + INT signals from the OS and shut
	// down the Request Manager when we receive them.
	if stopOnSignal {
		go s.waitForShutdown()
	}

	// Run the API - this will block until the API is stopped (or encounters
	// some fatal error). If the RunAPI hook has been provided, call that instead
	// of the default api.Run.
	var err error
	if s.appCtx.Hooks.RunAPI != nil {
		err = s.appCtx.Hooks.RunAPI()
	} else {
		err = s.api.Run()
	}

	// If the server was stopped (as opposed to some error within the API), wait
	// to make sure it's done shutting down the API and resumer before returning.
	if s.stopped {
		<-s.apiStopped
		<-s.resumerStopped
	}

	if err != nil {
		return fmt.Errorf("error from API: %s", err)
	}
	return nil
}

// Stop stops the running Request Resumer and API. It signals the resumer to shut
// down and then stops the API (using either the default api.Stop or the StopAPI
// hook if provided). Once Stop has been called, the server cannot be reused -
// future calls to Run will return an error.
//
// If stopOnSignal was set when calling Run, Stop will automatically be called by
// the server on receiving a TERM or INT signal from the OS. Otherwise, you must
// call Stop when you want to shut down the Request Manager.
func (s *Server) Stop() error {
	// Only stop once. We lock the whole Stop call, so that, if Stop is called
	// multiple times in quick succession, no calls will return before the server
	// has actually been shut down.
	s.stopMux.Lock()
	defer s.stopMux.Unlock()
	if s.stopped {
		return nil
	}
	s.stopped = true

	log.Infof("Stopping Request Manager server")

	// Stops the request resumer loop. The API will also begin refusing to start
	// running new requests.
	close(s.shutdownChan)

	// Stop the API, using the StopAPI hook if provided and api.Stop otherwise.
	var err error
	if s.appCtx.Hooks.StopAPI != nil {
		err = s.appCtx.Hooks.StopAPI()
	} else {
		err = s.api.Stop()
	}
	close(s.apiStopped) // indicate to Run that the API is done shutting down

	// Wait to return until the resumer has been stopped.
	<-s.resumerStopped

	if err != nil {
		return fmt.Errorf("error stopping API: %s", err)
	}
	return nil
}

// Boot sets up the server. It must be called before calling Run.
func (s *Server) Boot() error {
	// Load config file
	cfg, err := s.appCtx.Hooks.LoadConfig(s.appCtx)
	if err != nil {
		return fmt.Errorf("error loading config: %s", err)
	}
	// Override with env vars, if set
	cfg.Server.Addr = config.Env("SPINCYCLE_SERVER_ADDR", cfg.Server.Addr)
	cfg.Server.TLS.CertFile = config.Env("SPINCYCLE_SERVER_TLS_CERT_FILE", cfg.Server.TLS.CertFile)
	cfg.Server.TLS.KeyFile = config.Env("SPINCYCLE_SERVER_TLS_KEY_FILE", cfg.Server.TLS.KeyFile)
	cfg.Server.TLS.CAFile = config.Env("SPINCYCLE_SERVER_TLS_CA_FILE", cfg.Server.TLS.CAFile)
	cfg.MySQL.DSN = config.Env("SPINCYCLE_MYSQL_DSN", cfg.MySQL.DSN)
	cfg.Specs.Dir = config.Env("SPINCYCLE_SPECS_DIR", cfg.Specs.Dir)
	cfg.JRClient.ServerURL = config.Env("SPINCYCLE_JR_CLIENT_URL", cfg.JRClient.ServerURL)
	cfg.JRClient.TLS.CertFile = config.Env("SPINCYCLE_JR_CLIENT_TLS_CERT_FILE", cfg.JRClient.TLS.CertFile)
	cfg.JRClient.TLS.KeyFile = config.Env("SPINCYCLE_JR_CLIENT_TLS_KEY_FILE", cfg.JRClient.TLS.KeyFile)
	cfg.JRClient.TLS.CAFile = config.Env("SPINCYCLE_JR_CLIENT_TLS_CA_FILE", cfg.JRClient.TLS.CAFile)
	s.appCtx.Config = cfg
	cfgstr, _ := json.MarshalIndent(cfg, "", "  ")
	log.Printf("Config: %s", cfgstr)

	// Load and check requests specification files (specs)
	specs, err := s.appCtx.Hooks.LoadSpecs(s.appCtx)
	if err != nil {
		return fmt.Errorf("LoadSpecs: %s", err)
	}
	spec.ProcessSpecs(&specs)
	s.appCtx.Specs = specs

	checkFactories, err := s.appCtx.Factories.MakeCheckFactories(s.appCtx)
	if err != nil {
		return fmt.Errorf("MakeCheckFactories: %s", err)
	}
	checkFactories = append(checkFactories, spec.BaseCheckFactory{specs})
	checker, err := spec.NewChecker(checkFactories, log.Errorf)
	ok := checker.RunChecks(specs)
	if !ok {
		return fmt.Errorf("Static check(s) on request specification files failed; see log or run spinc-linter for details")
	}

	// Generator factory used to generate IDs for nodes in sequence graphs and jobs in job chains
	gf, err := s.appCtx.Factories.MakeIDGeneratorFactory(s.appCtx)
	if err != nil {
		return fmt.Errorf("MakeIDGeneratorFactory: %s", err)
	}

	// Do graph checks and get sequence graphs
	tg := graph.NewGrapher(specs, gf)
	seqGraphs, seqErrors := tg.CheckSequences()
	if len(seqErrors) != 0 {
		for seqName, seqError := range seqErrors {
			log.Errorf("%s: %s", seqName, seqError)
		}
		return fmt.Errorf("Graph check(s) on request specification files failed; see log or run spinc-linter for details")
	}

	// Resolver Factory: creates Resolvers, which resolve sequence graphs into request graphs
	resolverFactory := graph.NewResolverFactory(jobs.Factory, specs.Sequences, seqGraphs, gf)

	// Job Runner Client: how the Request Manager talks to Job Runners
	jrClient, err := s.appCtx.Factories.MakeJobRunnerClient(s.appCtx)
	if err != nil {
		return fmt.Errorf("MakeJobRunnerClient: %s", err)
	}

	// Db connection pool: for requests, job chains, etc. (pretty much everything)
	dbConnector, err := s.appCtx.Factories.MakeDbConnPool(s.appCtx)
	if err != nil {
		return fmt.Errorf("MakeDbConnPool: %s", err)
	}

	// Request Manager: core logic and coordination
	managerConfig := request.ManagerConfig{
		ResolverFactory: resolverFactory,
		Sequences:       specs.Sequences,
		DBConnector:     dbConnector,
		JRClient:        jrClient,
		DefaultJRURL:    s.appCtx.Config.JRClient.ServerURL,
		ShutdownChan:    s.shutdownChan,
	}
	s.appCtx.RM = request.NewManager(managerConfig)

	// Request Resumer: suspend + resume requests
	hostname, err := os.Hostname()
	if err != nil {
		return fmt.Errorf("error getting hostname: %s", err)
	}
	resumerConfig := request.ResumerConfig{
		RequestManager:       s.appCtx.RM,
		DBConnector:          dbConnector,
		JRClient:             jrClient,
		DefaultJRURL:         s.appCtx.Config.JRClient.ServerURL,
		RMHost:               hostname,
		ShutdownChan:         s.shutdownChan,
		SuspendedJobChainTTL: SJCTTL,
	}
	s.appCtx.RR = request.NewResumer(resumerConfig)

	// Status: figure out request status using db and Job Runners (real-time)
	s.appCtx.Status = status.NewManager(dbConnector, jrClient)

	// Job log store: save job log entries (JLE) from Job Runners
	s.appCtx.JLS = joblog.NewStore(dbConnector)

	// Auth Manager: request authorization (pre- (built-in) and post- using plugin)
	s.appCtx.Auth = auth.NewManager(s.appCtx.Plugins.Auth, mapACL(specs), cfg.Auth.AdminRoles, cfg.Auth.Strict)

	// API: endpoints and controllers, also handles auth via auth plugin
	s.api = api.NewAPI(s.appCtx)

	return nil
}

// API returns the Request Manager API created in Boot.
func (s *Server) API() *api.API {
	return s.api
}

// --------------------------------------------------------------------------

// Catch TERM and INT signals to gracefully shut down the Request Manager
func (s *Server) waitForShutdown() {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	<-sigChan

	err := s.Stop()
	if err != nil {
		log.Errorf("error shutting down server: %s", err)
	}
}

// MapACL maps spec file ACL to auth.ACL structure.
func mapACL(specs spec.Specs) map[string][]auth.ACL {
	acl := map[string][]auth.ACL{}
	for name, spec := range specs.Sequences {
		if len(spec.ACL) == 0 {
			acl[name] = nil
			continue
		}
		acl[name] = make([]auth.ACL, len(spec.ACL))
		for i, sa := range spec.ACL {
			acl[name][i] = auth.ACL{
				Role:  sa.Role,
				Admin: sa.Admin,
				Ops:   sa.Ops,
			}
		}
	}
	return acl
}
<commit_msg>Replace the password in the MySQL DSN with asterisks before logging<commit_after>// Copyright 2017-2019, Square, Inc.

// Package server bootstraps and runs the Request Manager.
package server

import (
	"encoding/json"
	"fmt"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/go-sql-driver/mysql"
	log "github.com/sirupsen/logrus"

	"github.com/square/spincycle/v2/config"
	"github.com/square/spincycle/v2/jobs"
	"github.com/square/spincycle/v2/request-manager/api"
	"github.com/square/spincycle/v2/request-manager/app"
	"github.com/square/spincycle/v2/request-manager/auth"
	"github.com/square/spincycle/v2/request-manager/graph"
	"github.com/square/spincycle/v2/request-manager/joblog"
	"github.com/square/spincycle/v2/request-manager/request"
	"github.com/square/spincycle/v2/request-manager/spec"
	"github.com/square/spincycle/v2/request-manager/status"
)

var (
	// How often the request resumer is run.
	ResumerInterval = 10 * time.Second

	// How long Suspended Job Chains have to be resumed before they're deleted.
	SJCTTL = 1 * time.Hour
)

type Server struct {
	appCtx app.Context
	api    *api.API

	shutdownChan   chan struct{}
	resumerStopped chan struct{}
	apiStopped     chan struct{}
	stopped        bool
	stopMux        sync.Mutex
}

func NewServer(appCtx app.Context) *Server {
	return &Server{
		appCtx:         appCtx,
		resumerStopped: make(chan struct{}),
		apiStopped:     make(chan struct{}),
		shutdownChan:   make(chan struct{}),
		stopMux:        sync.Mutex{},
	}
}

// Run runs the Request Manager API and Request Resumer. It returns when the API
// stops running (either from an error, or after a call to Stop). If a custom RunAPI
// hook has been provided, it will be called to run the API instead of the default
// api.Run.
//
// If stopOnSignal = true, the server will listen for TERM and INT signals from the
// OS and call Stop to shut itself down when those signals are received. Else, the
// caller must call Stop to shut down the server.
func (s *Server) Run(stopOnSignal bool) error {
	if s.api == nil {
		panic("Server.Run called before Server.Boot")
	}
	if s.stopped {
		return fmt.Errorf("server stopped")
	}

	// Run the request resumer in a goroutine, so we can block on running the api.
	go func() {
		defer close(s.resumerStopped) // indicate the resumer is done running

		// Every 10 seconds until the server is stopped, resume all Suspended Job
		// Chains and clean up any that are in a bad state.
		ticker := time.NewTicker(ResumerInterval)
	RESUMER:
		for {
			select {
			case <-s.shutdownChan:
				break RESUMER
			case <-ticker.C:
				s.appCtx.RR.ResumeAll()
				s.appCtx.RR.Cleanup()
			}
		}
		ticker.Stop()
	}()

	// If stopOnSignal = true, watch for TERM + INT signals from the OS and shut
	// down the Request Manager when we receive them.
	if stopOnSignal {
		go s.waitForShutdown()
	}

	// Run the API - this will block until the API is stopped (or encounters
	// some fatal error). If the RunAPI hook has been provided, call that instead
	// of the default api.Run.
	var err error
	if s.appCtx.Hooks.RunAPI != nil {
		err = s.appCtx.Hooks.RunAPI()
	} else {
		err = s.api.Run()
	}

	// If the server was stopped (as opposed to some error within the API), wait
	// to make sure it's done shutting down the API and resumer before returning.
	if s.stopped {
		<-s.apiStopped
		<-s.resumerStopped
	}

	if err != nil {
		return fmt.Errorf("error from API: %s", err)
	}
	return nil
}

// Stop stops the running Request Resumer and API. It signals the resumer to shut
// down and then stops the API (using either the default api.Stop or the StopAPI
// hook if provided). Once Stop has been called, the server cannot be reused -
// future calls to Run will return an error.
//
// If stopOnSignal was set when calling Run, Stop will automatically be called by
// the server on receiving a TERM or INT signal from the OS. Otherwise, you must
// call Stop when you want to shut down the Request Manager.
func (s *Server) Stop() error {
	// Only stop once. We lock the whole Stop call, so that, if Stop is called
	// multiple times in quick succession, no calls will return before the server
	// has actually been shut down.
	s.stopMux.Lock()
	defer s.stopMux.Unlock()
	if s.stopped {
		return nil
	}
	s.stopped = true

	log.Infof("Stopping Request Manager server")

	// Stops the request resumer loop. The API will also begin refusing to start
	// running new requests.
	close(s.shutdownChan)

	// Stop the API, using the StopAPI hook if provided and api.Stop otherwise.
	var err error
	if s.appCtx.Hooks.StopAPI != nil {
		err = s.appCtx.Hooks.StopAPI()
	} else {
		err = s.api.Stop()
	}
	close(s.apiStopped) // indicate to Run that the API is done shutting down

	// Wait to return until the resumer has been stopped.
	<-s.resumerStopped

	if err != nil {
		return fmt.Errorf("error stopping API: %s", err)
	}
	return nil
}

// Boot sets up the server. It must be called before calling Run.
func (s *Server) Boot() error {
	// Load config file
	cfg, err := s.appCtx.Hooks.LoadConfig(s.appCtx)
	if err != nil {
		return fmt.Errorf("error loading config: %s", err)
	}
	// Override with env vars, if set
	cfg.Server.Addr = config.Env("SPINCYCLE_SERVER_ADDR", cfg.Server.Addr)
	cfg.Server.TLS.CertFile = config.Env("SPINCYCLE_SERVER_TLS_CERT_FILE", cfg.Server.TLS.CertFile)
	cfg.Server.TLS.KeyFile = config.Env("SPINCYCLE_SERVER_TLS_KEY_FILE", cfg.Server.TLS.KeyFile)
	cfg.Server.TLS.CAFile = config.Env("SPINCYCLE_SERVER_TLS_CA_FILE", cfg.Server.TLS.CAFile)
	cfg.MySQL.DSN = config.Env("SPINCYCLE_MYSQL_DSN", cfg.MySQL.DSN)
	cfg.Specs.Dir = config.Env("SPINCYCLE_SPECS_DIR", cfg.Specs.Dir)
	cfg.JRClient.ServerURL = config.Env("SPINCYCLE_JR_CLIENT_URL", cfg.JRClient.ServerURL)
	cfg.JRClient.TLS.CertFile = config.Env("SPINCYCLE_JR_CLIENT_TLS_CERT_FILE", cfg.JRClient.TLS.CertFile)
	cfg.JRClient.TLS.KeyFile = config.Env("SPINCYCLE_JR_CLIENT_TLS_KEY_FILE", cfg.JRClient.TLS.KeyFile)
	cfg.JRClient.TLS.CAFile = config.Env("SPINCYCLE_JR_CLIENT_TLS_CA_FILE", cfg.JRClient.TLS.CAFile)
	s.appCtx.Config = cfg

	// Log the config. If a password exists in the MySQL DSN, obfuscate it before logging.
	logCfg := cfg // Create a copy of cfg since we may mutate it.
	dsn, err := mysql.ParseDSN(logCfg.MySQL.DSN)
	if err != nil {
		return fmt.Errorf("error parsing MySQL DSN: %s", err)
	}
	if dsn.Passwd != "" {
		dsn.Passwd = "*****"
		logCfg.MySQL.DSN = dsn.FormatDSN()
	}
	cfgstr, _ := json.MarshalIndent(logCfg, "", "  ")
	log.Printf("Config: %s", cfgstr)

	// Load and check requests specification files (specs)
	specs, err := s.appCtx.Hooks.LoadSpecs(s.appCtx)
	if err != nil {
		return fmt.Errorf("LoadSpecs: %s", err)
	}
	spec.ProcessSpecs(&specs)
	s.appCtx.Specs = specs

	checkFactories, err := s.appCtx.Factories.MakeCheckFactories(s.appCtx)
	if err != nil {
		return fmt.Errorf("MakeCheckFactories: %s", err)
	}
	checkFactories = append(checkFactories, spec.BaseCheckFactory{specs})
	checker, err := spec.NewChecker(checkFactories, log.Errorf)
	ok := checker.RunChecks(specs)
	if !ok {
		return fmt.Errorf("Static check(s) on request specification files failed; see log or run spinc-linter for details")
	}

	// Generator factory used to generate IDs for nodes in sequence graphs and jobs in job chains
	gf, err := s.appCtx.Factories.MakeIDGeneratorFactory(s.appCtx)
	if err != nil {
		return fmt.Errorf("MakeIDGeneratorFactory: %s", err)
	}

	// Do graph checks and get sequence graphs
	tg := graph.NewGrapher(specs, gf)
	seqGraphs, seqErrors := tg.CheckSequences()
	if len(seqErrors) != 0 {
		for seqName, seqError := range seqErrors {
			log.Errorf("%s: %s", seqName, seqError)
		}
		return fmt.Errorf("Graph check(s) on request specification files failed; see log or run spinc-linter for details")
	}

	// Resolver Factory: creates Resolvers, which resolve sequence graphs into request graphs
	resolverFactory := graph.NewResolverFactory(jobs.Factory, specs.Sequences, seqGraphs, gf)

	// Job Runner Client: how the Request Manager talks to Job Runners
	jrClient, err := s.appCtx.Factories.MakeJobRunnerClient(s.appCtx)
	if err != nil {
		return fmt.Errorf("MakeJobRunnerClient: %s", err)
	}

	// Db connection pool: for requests, job chains, etc. (pretty much everything)
	dbConnector, err := s.appCtx.Factories.MakeDbConnPool(s.appCtx)
	if err != nil {
		return fmt.Errorf("MakeDbConnPool: %s", err)
	}

	// Request Manager: core logic and coordination
	managerConfig := request.ManagerConfig{
		ResolverFactory: resolverFactory,
		Sequences:       specs.Sequences,
		DBConnector:     dbConnector,
		JRClient:        jrClient,
		DefaultJRURL:    s.appCtx.Config.JRClient.ServerURL,
		ShutdownChan:    s.shutdownChan,
	}
	s.appCtx.RM = request.NewManager(managerConfig)

	// Request Resumer: suspend + resume requests
	hostname, err := os.Hostname()
	if err != nil {
		return fmt.Errorf("error getting hostname: %s", err)
	}
	resumerConfig := request.ResumerConfig{
		RequestManager:       s.appCtx.RM,
		DBConnector:          dbConnector,
		JRClient:             jrClient,
		DefaultJRURL:         s.appCtx.Config.JRClient.ServerURL,
		RMHost:               hostname,
		ShutdownChan:         s.shutdownChan,
		SuspendedJobChainTTL: SJCTTL,
	}
	s.appCtx.RR = request.NewResumer(resumerConfig)

	// Status: figure out request status using db and Job Runners (real-time)
	s.appCtx.Status = status.NewManager(dbConnector, jrClient)

	// Job log store: save job log entries (JLE) from Job Runners
	s.appCtx.JLS = joblog.NewStore(dbConnector)

	// Auth Manager: request authorization (pre- (built-in) and post- using plugin)
	s.appCtx.Auth = auth.NewManager(s.appCtx.Plugins.Auth, mapACL(specs), cfg.Auth.AdminRoles, cfg.Auth.Strict)

	// API: endpoints and controllers, also handles auth via auth plugin
	s.api = api.NewAPI(s.appCtx)

	return nil
}

// API returns the Request Manager API created in Boot.
func (s *Server) API() *api.API {
	return s.api
}

// --------------------------------------------------------------------------

// Catch TERM and INT signals to gracefully shut down the Request Manager
func (s *Server) waitForShutdown() {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	<-sigChan

	err := s.Stop()
	if err != nil {
		log.Errorf("error shutting down server: %s", err)
	}
}

// MapACL maps spec file ACL to auth.ACL structure.
func mapACL(specs spec.Specs) map[string][]auth.ACL {
	acl := map[string][]auth.ACL{}
	for name, spec := range specs.Sequences {
		if len(spec.ACL) == 0 {
			acl[name] = nil
			continue
		}
		acl[name] = make([]auth.ACL, len(spec.ACL))
		for i, sa := range spec.ACL {
			acl[name][i] = auth.ACL{
				Role:  sa.Role,
				Admin: sa.Admin,
				Ops:   sa.Ops,
			}
		}
	}
	return acl
}
<|endoftext|>


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

package othttp

import (
	"io"
	"net/http"

	"go.opentelemetry.io/otel/api/global"
	"go.opentelemetry.io/otel/api/kv"
	"go.opentelemetry.io/otel/api/propagation"
	"go.opentelemetry.io/otel/api/standard"
	"go.opentelemetry.io/otel/api/trace"
)

var _ http.Handler = &Handler{}

// Handler is http middleware that corresponds to the http.Handler interface and
// is designed to wrap a http.Mux (or equivalent), while individual routes on
// the mux are wrapped with WithRouteTag. A Handler will add various attributes
// to the span using the kv.Keys defined in this package.
type Handler struct {
	operation string
	handler   http.Handler

	tracer            trace.Tracer
	propagators       propagation.Propagators
	spanStartOptions  []trace.StartOption
	readEvent         bool
	writeEvent        bool
	filters           []Filter
	spanNameFormatter func(string, *http.Request) string
}

func defaultHandlerFormatter(operation string, _ *http.Request) string {
	return operation
}

// NewHandler wraps the passed handler, functioning like middleware, in a span
// named after the operation and with any provided Options.
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
	h := Handler{
		handler:   handler,
		operation: operation,
	}

	defaultOpts := []Option{
		WithTracer(global.Tracer("go.opentelemetry.io/otel/instrumentation/othttp")),
		WithPropagators(global.Propagators()),
		WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
		WithSpanNameFormatter(defaultHandlerFormatter),
	}

	c := NewConfig(append(defaultOpts, opts...)...)
	h.configure(c)

	return &h
}

func (h *Handler) configure(c *Config) {
	h.tracer = c.Tracer
	h.propagators = c.Propagators
	h.spanStartOptions = c.SpanStartOptions
	h.readEvent = c.ReadEvent
	h.writeEvent = c.WriteEvent
	h.filters = c.Filters
	h.spanNameFormatter = c.SpanNameFormatter
}

// ServeHTTP serves HTTP requests (http.Handler)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	for _, f := range h.filters {
		if !f(r) {
			// Simply pass through to the handler if a filter rejects the request
			h.handler.ServeHTTP(w, r)
			return
		}
	}

	opts := append([]trace.StartOption{
		trace.WithAttributes(standard.NetAttributesFromHTTPRequest("tcp", r)...),
		trace.WithAttributes(standard.EndUserAttributesFromHTTPRequest(r)...),
		trace.WithAttributes(standard.HTTPServerAttributesFromHTTPRequest(h.operation, "", r)...),
	}, h.spanStartOptions...) // start with the configured options

	ctx := propagation.ExtractHTTP(r.Context(), h.propagators, r.Header)
	ctx, span := h.tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
	defer span.End()

	readRecordFunc := func(int64) {}
	if h.readEvent {
		readRecordFunc = func(n int64) {
			span.AddEvent(ctx, "read", ReadBytesKey.Int64(n))
		}
	}
	bw := bodyWrapper{ReadCloser: r.Body, record: readRecordFunc}
	r.Body = &bw

	writeRecordFunc := func(int64) {}
	if h.writeEvent {
		writeRecordFunc = func(n int64) {
			span.AddEvent(ctx, "write", WroteBytesKey.Int64(n))
		}
	}

	rww := &respWriterWrapper{ResponseWriter: w, record: writeRecordFunc, ctx: ctx, props: h.propagators}

	h.handler.ServeHTTP(rww, r.WithContext(ctx))

	setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err)
}

func setAfterServeAttributes(span trace.Span, read, wrote int64, statusCode int, rerr, werr error) {
	kv := []kv.KeyValue{}

	// TODO: Consider adding an event after each read and write, possibly as an
	// option (defaulting to off), so as to not create needlessly verbose spans.
	if read > 0 {
		kv = append(kv, ReadBytesKey.Int64(read))
	}
	if rerr != nil && rerr != io.EOF {
		kv = append(kv, ReadErrorKey.String(rerr.Error()))
	}
	if wrote > 0 {
		kv = append(kv, WroteBytesKey.Int64(wrote))
	}
	if statusCode > 0 {
		kv = append(kv, standard.HTTPAttributesFromHTTPStatusCode(statusCode)...)
	}
	if werr != nil && werr != io.EOF {
		kv = append(kv, WriteErrorKey.String(werr.Error()))
	}
	span.SetAttributes(kv...)
}

// WithRouteTag annotates a span with the provided route name using the
// RouteKey Tag.
func WithRouteTag(route string, h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		span := trace.SpanFromContext(r.Context())
		span.SetAttributes(standard.HTTPRouteKey.String(route))
		h.ServeHTTP(w, r)
	})
}
<commit_msg>set othttp span status from HTTP status code (#832)<commit_after>// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package othttp

import (
	"io"
	"net/http"

	"go.opentelemetry.io/otel/api/global"
	"go.opentelemetry.io/otel/api/kv"
	"go.opentelemetry.io/otel/api/propagation"
	"go.opentelemetry.io/otel/api/standard"
	"go.opentelemetry.io/otel/api/trace"
)

var _ http.Handler = &Handler{}

// Handler is http middleware that corresponds to the http.Handler interface and
// is designed to wrap a http.Mux (or equivalent), while individual routes on
// the mux are wrapped with WithRouteTag. A Handler will add various attributes
// to the span using the kv.Keys defined in this package.
type Handler struct {
	operation string
	handler   http.Handler

	tracer            trace.Tracer
	propagators       propagation.Propagators
	spanStartOptions  []trace.StartOption
	readEvent         bool
	writeEvent        bool
	filters           []Filter
	spanNameFormatter func(string, *http.Request) string
}

func defaultHandlerFormatter(operation string, _ *http.Request) string {
	return operation
}

// NewHandler wraps the passed handler, functioning like middleware, in a span
// named after the operation and with any provided Options.
func NewHandler(handler http.Handler, operation string, opts ...Option) http.Handler {
	h := Handler{
		handler:   handler,
		operation: operation,
	}

	defaultOpts := []Option{
		WithTracer(global.Tracer("go.opentelemetry.io/otel/instrumentation/othttp")),
		WithPropagators(global.Propagators()),
		WithSpanOptions(trace.WithSpanKind(trace.SpanKindServer)),
		WithSpanNameFormatter(defaultHandlerFormatter),
	}

	c := NewConfig(append(defaultOpts, opts...)...)
	h.configure(c)

	return &h
}

func (h *Handler) configure(c *Config) {
	h.tracer = c.Tracer
	h.propagators = c.Propagators
	h.spanStartOptions = c.SpanStartOptions
	h.readEvent = c.ReadEvent
	h.writeEvent = c.WriteEvent
	h.filters = c.Filters
	h.spanNameFormatter = c.SpanNameFormatter
}

// ServeHTTP serves HTTP requests (http.Handler)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	for _, f := range h.filters {
		if !f(r) {
			// Simply pass through to the handler if a filter rejects the request
			h.handler.ServeHTTP(w, r)
			return
		}
	}

	opts := append([]trace.StartOption{
		trace.WithAttributes(standard.NetAttributesFromHTTPRequest("tcp", r)...),
		trace.WithAttributes(standard.EndUserAttributesFromHTTPRequest(r)...),
		trace.WithAttributes(standard.HTTPServerAttributesFromHTTPRequest(h.operation, "", r)...),
	}, h.spanStartOptions...) // start with the configured options

	ctx := propagation.ExtractHTTP(r.Context(), h.propagators, r.Header)
	ctx, span := h.tracer.Start(ctx, h.spanNameFormatter(h.operation, r), opts...)
	defer span.End()

	readRecordFunc := func(int64) {}
	if h.readEvent {
		readRecordFunc = func(n int64) {
			span.AddEvent(ctx, "read", ReadBytesKey.Int64(n))
		}
	}
	bw := bodyWrapper{ReadCloser: r.Body, record: readRecordFunc}
	r.Body = &bw

	writeRecordFunc := func(int64) {}
	if h.writeEvent {
		writeRecordFunc = func(n int64) {
			span.AddEvent(ctx, "write", WroteBytesKey.Int64(n))
		}
	}

	rww := &respWriterWrapper{ResponseWriter: w, record: writeRecordFunc, ctx: ctx, props: h.propagators}

	h.handler.ServeHTTP(rww, r.WithContext(ctx))

	setAfterServeAttributes(span, bw.read, rww.written, rww.statusCode, bw.err, rww.err)
	span.SetStatus(standard.SpanStatusFromHTTPStatusCode(rww.statusCode))
}

func setAfterServeAttributes(span trace.Span, read, wrote int64, statusCode int, rerr, werr error) {
	kv := []kv.KeyValue{}

	// TODO: Consider adding an event after each read and write, possibly as an
	// option (defaulting to off), so as to not create needlessly verbose spans.
	if read > 0 {
		kv = append(kv, ReadBytesKey.Int64(read))
	}
	if rerr != nil && rerr != io.EOF {
		kv = append(kv, ReadErrorKey.String(rerr.Error()))
	}
	if wrote > 0 {
		kv = append(kv, WroteBytesKey.Int64(wrote))
	}
	if statusCode > 0 {
		kv = append(kv, standard.HTTPAttributesFromHTTPStatusCode(statusCode)...)
	}
	if werr != nil && werr != io.EOF {
		kv = append(kv, WriteErrorKey.String(werr.Error()))
	}
	span.SetAttributes(kv...)
}

// WithRouteTag annotates a span with the provided route name using the
// RouteKey Tag.
func WithRouteTag(route string, h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		span := trace.SpanFromContext(r.Context())
		span.SetAttributes(standard.HTTPRouteKey.String(route))
		h.ServeHTTP(w, r)
	})
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform/helper/diff"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/goamz/ec2"
)

func resource_aws_internet_gateway_create(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Merge the diff so that we have all the proper attributes
	s = s.MergeDiff(d)

	// Create the gateway
	log.Printf("[DEBUG] Creating internet gateway")
	resp, err := ec2conn.CreateInternetGateway(nil)
	if err != nil {
		return nil, fmt.Errorf("Error creating subnet: %s", err)
	}

	// Get the ID and store it
	ig := &resp.InternetGateway
	s.ID = ig.InternetGatewayId
	log.Printf("[INFO] InternetGateway ID: %s", s.ID)

	// Update our attributes and return
	return resource_aws_internet_gateway_update_state(s, ig)
}

func resource_aws_internet_gateway_update(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff,
	meta interface{}) (*terraform.ResourceState, error) {
	// This should never be called because we have no update-able
	// attributes
	panic("Update for internet gateway is not supported")

	return nil, nil
}

func resource_aws_internet_gateway_destroy(
	s *terraform.ResourceState,
	meta interface{}) error {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	log.Printf("[INFO] Deleting Internet Gateway: %s", s.ID)
	if _, err := ec2conn.DeleteInternetGateway(s.ID); err != nil {
		ec2err, ok := err.(*ec2.Error)
		if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
			return nil
		}

		return fmt.Errorf("Error deleting internet gateway: %s", err)
	}

	// Wait for the internet gateway to actually delete
	log.Printf("[DEBUG] Waiting for internet gateway (%s) to delete", s.ID)
	stateConf := &resource.StateChangeConf{
		Pending: []string{"available"},
		Target:  "",
		Refresh: IGStateRefreshFunc(ec2conn, s.ID),
		Timeout: 10 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for internet gateway (%s) to destroy",
			s.ID, err)
	}

	return nil
}

func resource_aws_internet_gateway_refresh(
	s *terraform.ResourceState,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	igRaw, _, err := IGStateRefreshFunc(ec2conn, s.ID)()
	if err != nil {
		return s, err
	}
	if igRaw == nil {
		return nil, nil
	}

	ig := igRaw.(*ec2.InternetGateway)
	return resource_aws_internet_gateway_update_state(s, ig)
}

func resource_aws_internet_gateway_diff(
	s *terraform.ResourceState,
	c *terraform.ResourceConfig,
	meta interface{}) (*terraform.ResourceDiff, error) {
	b := &diff.ResourceBuilder{
		Attrs: map[string]diff.AttrType{},
	}

	return b.Diff(s, c)
}

func resource_aws_internet_gateway_update_state(
	s *terraform.ResourceState,
	ig *ec2.InternetGateway) (*terraform.ResourceState, error) {
	return s, nil
}

// IGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// an internet gateway.
func IGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		resp, err := conn.DescribeInternetGateways([]string{id}, ec2.NewFilter())
		if err != nil {
			ec2err, ok := err.(*ec2.Error)
			if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
				resp = nil
			} else {
				log.Printf("[ERROR] Error on IGStateRefresh: %s", err)
				return nil, "", err
			}
		}

		if resp == nil {
			// Sometimes AWS just has consistency issues and doesn't see
			// our instance yet. Return an empty state.
			return nil, "", nil
		}

		ig := &resp.InternetGateways[0]
		return ig, "available", nil
	}
}
<commit_msg>providers/aws: support attaching/detaching internet gateway<commit_after>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform/helper/diff"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/goamz/ec2"
)

func resource_aws_internet_gateway_create(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Create the gateway
	log.Printf("[DEBUG] Creating internet gateway")
	resp, err := ec2conn.CreateInternetGateway(nil)
	if err != nil {
		return nil, fmt.Errorf("Error creating subnet: %s", err)
	}

	// Get the ID and store it
	ig := &resp.InternetGateway
	s.ID = ig.InternetGatewayId
	log.Printf("[INFO] InternetGateway ID: %s", s.ID)

	// Update our attributes and return
	return resource_aws_internet_gateway_update(s, d, meta)
}

func resource_aws_internet_gateway_update(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Merge the diff so we have the latest attributes
	rs := s.MergeDiff(d)

	// If we're already attached, detach it first
	if s.Attributes["vpc_id"] != "" {
		log.Printf(
			"[INFO] Detaching Internet Gateway '%s' from VPC '%s'",
			s.ID,
			s.Attributes["vpc_id"])
		_, err := ec2conn.DetachInternetGateway(s.ID, s.Attributes["vpc_id"])
		if err != nil {
			return s, err
		}

		delete(s.Attributes, "vpc_id")
	}

	// A note on the states below: the AWS docs (as of July, 2014) say
	// that the states would be: attached, attaching, detached, detaching,
	// but when running, I noticed that the state is usually "available" when
	// it is attached.

	// Wait for it to be fully detached before continuing
	log.Printf("[DEBUG] Waiting for internet gateway (%s) to detach", s.ID)
	stateConf := &resource.StateChangeConf{
		Pending: []string{"attached", "detaching", "available"},
		Target:  "detached",
		Refresh: IGAttachStateRefreshFunc(ec2conn, s.ID),
		Timeout: 1 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return s, fmt.Errorf(
			"Error waiting for internet gateway (%s) to detach: %s",
			s.ID, err)
	}

	// Attach
	log.Printf(
		"[INFO] Attaching Internet Gateway '%s' to VPC '%s'",
		rs.ID,
		rs.Attributes["vpc_id"])
	_, err := ec2conn.AttachInternetGateway(rs.ID, rs.Attributes["vpc_id"])
	if err != nil {
		return s, err
	}

	// Wait for it to be fully attached before continuing
	log.Printf("[DEBUG] Waiting for internet gateway (%s) to attach", s.ID)
	stateConf = &resource.StateChangeConf{
		Pending: []string{"detached", "attaching"},
		Target:  "available",
		Refresh: IGAttachStateRefreshFunc(ec2conn, s.ID),
		Timeout: 1 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return rs, fmt.Errorf(
			"Error waiting for internet gateway (%s) to attach: %s",
			rs.ID, err)
	}

	return rs, nil
}

func resource_aws_internet_gateway_destroy(
	s *terraform.ResourceState,
	meta interface{}) error {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	log.Printf("[INFO] Deleting Internet Gateway: %s", s.ID)
	if _, err := ec2conn.DeleteInternetGateway(s.ID); err != nil {
		ec2err, ok := err.(*ec2.Error)
		if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
			return nil
		}

		return fmt.Errorf("Error deleting internet gateway: %s", err)
	}

	// Wait for the internet gateway to actually delete
	log.Printf("[DEBUG] Waiting for internet gateway (%s) to delete", s.ID)
	stateConf := &resource.StateChangeConf{
		Pending: []string{"available"},
		Target:  "",
		Refresh: IGStateRefreshFunc(ec2conn, s.ID),
		Timeout: 10 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for internet gateway (%s) to destroy",
			s.ID, err)
	}

	return nil
}

func resource_aws_internet_gateway_refresh(
	s *terraform.ResourceState,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	igRaw, _, err := IGStateRefreshFunc(ec2conn, s.ID)()
	if err != nil {
		return s, err
	}
	if igRaw == nil {
		return nil, nil
	}

	ig := igRaw.(*ec2.InternetGateway)
	return resource_aws_internet_gateway_update_state(s, ig)
}

func resource_aws_internet_gateway_diff(
	s *terraform.ResourceState,
	c *terraform.ResourceConfig,
	meta interface{}) (*terraform.ResourceDiff, error) {
	b := &diff.ResourceBuilder{
		Attrs: map[string]diff.AttrType{
			"vpc_id": diff.AttrTypeUpdate,
		},
	}

	return b.Diff(s, c)
}

func resource_aws_internet_gateway_update_state(
	s *terraform.ResourceState,
	ig *ec2.InternetGateway) (*terraform.ResourceState, error) {
	return s, nil
}

// IGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// an internet gateway.
func IGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		resp, err := conn.DescribeInternetGateways([]string{id}, ec2.NewFilter())
		if err != nil {
			ec2err, ok := err.(*ec2.Error)
			if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
				resp = nil
			} else {
				log.Printf("[ERROR] Error on IGStateRefresh: %s", err)
				return nil, "", err
			}
		}

		if resp == nil {
			// Sometimes AWS just has consistency issues and doesn't see
			// our instance yet. Return an empty state.
			return nil, "", nil
		}

		ig := &resp.InternetGateways[0]
		return ig, "available", nil
	}
}

// IGAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used
// watch the state of an internet gateway's attachment.
func IGAttachStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		resp, err := conn.DescribeInternetGateways([]string{id}, ec2.NewFilter())
		if err != nil {
			ec2err, ok := err.(*ec2.Error)
			if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
				resp = nil
			} else {
				log.Printf("[ERROR] Error on IGStateRefresh: %s", err)
				return nil, "", err
			}
		}

		if resp == nil {
			// Sometimes AWS just has consistency issues and doesn't see
			// our instance yet. Return an empty state.
			return nil, "", nil
		}

		ig := &resp.InternetGateways[0]

		if len(ig.Attachments) == 0 {
			// No attachments, we're detached
			return ig, "detached", nil
		}

		return ig, ig.Attachments[0].State, nil
	}
}
<|endoftext|>


<commit_before>// Copyright (C) 2017 File Maps Backend Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package main

import (
	"bytes"
	"compress/gzip"
	"encoding/base64"
	log "github.com/Sirupsen/logrus"
	"go/format"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"text/template"
)

const (
	target = "github.com/filemaps/filemaps-backend/cmd/filemaps"
)

var tmpl = template.Must(template.New("assets").Parse(`package httpd

import "encoding/base64"

func GetAssets() map[string][]byte {
	var assets = make(map[string][]byte, {{.Assets | len}})
{{range $asset := .Assets}}
	assets["{{$asset.Name}}"], _ = base64.StdEncoding.DecodeString("{{$asset.Content}}"){{end}}
	return assets
}
`))

type asset struct {
	Name    string
	Content string
}

type tmplVars struct {
	Assets []asset
}

var (
	assets []asset
)

func main() {
	log.Info("Building and installing File Maps")
	packageWebUI("filemaps-webui/src/", "pkg/httpd/webui.go")
	run("go", "install", target)
}

func run(cmd string, args ...string) {
	cmdh := exec.Command(cmd, args...)
	cmdh.Stdout = os.Stdout
	cmdh.Stderr = os.Stderr
	err := cmdh.Run()
	if err != nil {
		log.WithFields(log.Fields{
			"cmd":  cmd,
			"args": args,
		}).Error(err)
	}
}

func getWalkFunc(base string) filepath.WalkFunc {
	return func(name string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if strings.HasPrefix(filepath.Base(name), ".") {
			// ignore files beginning with dot
			return nil
		}

		if info.Mode().IsRegular() {
			f, err := os.Open(name)
			if err != nil {
				return err
			}

			// read file contents and gzip it to buffer
			var buf bytes.Buffer
			g := gzip.NewWriter(&buf)
			io.Copy(g, f)
			f.Close()
			g.Flush()
			g.Close()

			// create asset struct and append it to vars
			name, _ = filepath.Rel(base, name)
			assets = append(assets, asset{
				Name:    filepath.ToSlash(name),
				Content: base64.StdEncoding.EncodeToString(buf.Bytes()),
			})
		}
		return nil
	}
}

// packageWebUI packages Web UI files into a single go file.
// All files are gzipped and base64 encoded into static strings.
func packageWebUI(path string, out string) {
	log.Info("Packaging Web UI")
	filepath.Walk(path, getWalkFunc(path))

	var buf bytes.Buffer
	tmpl.Execute(&buf, tmplVars{
		Assets: assets,
	})

	bs, err := format.Source(buf.Bytes())
	if err != nil {
		panic(err)
	}
	err = ioutil.WriteFile(out, bs, 0644)
	if err != nil {
		panic(err)
	}
	log.WithFields(log.Fields{
		"output": out,
	}).Info("Web UI packaged")
}
<commit_msg>build tweaking<commit_after>// Copyright (C) 2017 File Maps Backend Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package main

import (
	"bytes"
	"compress/gzip"
	"encoding/base64"
	log "github.com/Sirupsen/logrus"
	"go/format"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"text/template"
)

const (
	target    = "github.com/filemaps/filemaps-backend/cmd/filemaps"
	webuiPath = "filemaps-webui/src/"
)

var tmpl = template.Must(template.New("assets").Parse(`package httpd

import "encoding/base64"

func GetAssets() map[string][]byte {
	var assets = make(map[string][]byte, {{.Assets | len}})
{{range $asset := .Assets}}
	assets["{{$asset.Name}}"], _ = base64.StdEncoding.DecodeString("{{$asset.Content}}"){{end}}
	return assets
}
`))

type asset struct {
	Name    string
	Content string
}

type tmplVars struct {
	Assets []asset
}

var (
	assets []asset
)

func main() {
	log.Info("Building and installing File Maps")
	packageWebUI(webuiPath, "pkg/httpd/webui.go")
	run("go", "install", target)
}

func run(cmd string, args ...string) {
	cmdh := exec.Command(cmd, args...)
	cmdh.Stdout = os.Stdout
	cmdh.Stderr = os.Stderr
	err := cmdh.Run()
	if err != nil {
		log.WithFields(log.Fields{
			"cmd":  cmd,
			"args": args,
		}).Error(err)
	}
}

func getWalkFunc(base string) filepath.WalkFunc {
	return func(name string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if strings.HasPrefix(filepath.Base(name), ".") {
			// ignore files beginning with dot
			return nil
		}

		if info.Mode().IsRegular() {
			f, err := os.Open(name)
			if err != nil {
				return err
			}

			// read file contents and gzip it to buffer
			var buf bytes.Buffer
			g := gzip.NewWriter(&buf)
			io.Copy(g, f)
			f.Close()
			g.Flush()
			g.Close()

			// create asset struct and append it to vars
			name, _ = filepath.Rel(base, name)
			assets = append(assets, asset{
				Name:    filepath.ToSlash(name),
				Content: base64.StdEncoding.EncodeToString(buf.Bytes()),
			})
		}
		return nil
	}
}

// packageWebUI packages Web UI files into a single go file.
// All files are gzipped and base64 encoded into static strings.
func packageWebUI(path string, out string) {
	log.Info("Packaging Web UI")
	filepath.Walk(path, getWalkFunc(path))

	if len(assets) == 0 {
		log.WithFields(log.Fields{
			"path": path,
		}).Fatal("No Web UI files found. Make sure you have installed them into path")
		return
	}
	var buf bytes.Buffer
	tmpl.Execute(&buf, tmplVars{
		Assets: assets,
	})

	bs, err := format.Source(buf.Bytes())
	if err != nil {
		panic(err)
	}
	err = ioutil.WriteFile(out, bs, 0644)
	if err != nil {
		panic(err)
	}
	log.WithFields(log.Fields{
		"output": out,
	}).Info("Web UI packaged")
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"os"

	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/s3"
)

func resourceAwsS3BucketObject() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsS3BucketObjectPut,
		Read:   resourceAwsS3BucketObjectRead,
		Delete: resourceAwsS3BucketObjectDelete,

		Schema: map[string]*schema.Schema{
			"bucket": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"cache_control": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"content_disposition": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"content_encoding": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"content_language": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"content_type": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"key": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"source": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"etag": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) error {
	s3conn := meta.(*AWSClient).s3conn

	bucket := d.Get("bucket").(string)
	key := d.Get("key").(string)

	source := d.Get("source").(string)
	file, err := os.Open(source)

	if err != nil {
		return fmt.Errorf("Error opening S3 bucket object source (%s): %s", source, err)
	}
	putInput := &s3.PutObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
		Body:   file,
	}

	if v, ok := d.GetOk("cache_control"); ok {
		putInput.CacheControl = aws.String(v.(string))
	}

	if v, ok := d.GetOk("content_type"); ok {
		putInput.ContentType = aws.String(v.(string))
	}

	if v, ok := d.GetOk("content_encoding"); ok {
		putInput.ContentEncoding = aws.String(v.(string))
	}

	if v, ok := d.GetOk("content_language"); ok {
		putInput.ContentLanguage = aws.String(v.(string))
	}

	if v, ok := d.GetOk("content_disposition"); ok {
		putInput.ContentDisposition = aws.String(v.(string))
	}

	resp, err := s3conn.PutObject(putInput)

	if err != nil {
		return fmt.Errorf("Error putting object in S3 bucket (%s): %s", bucket, err)
	}

	d.Set("etag", resp.ETag)
	d.SetId(key)
	return nil
}

func resourceAwsS3BucketObjectRead(d *schema.ResourceData, meta interface{}) error {
	s3conn := meta.(*AWSClient).s3conn

	bucket := d.Get("bucket").(string)
	key := d.Get("key").(string)
	etag := d.Get("etag").(string)

	resp, err := s3conn.HeadObject(
		&s3.HeadObjectInput{
			Bucket:  aws.String(bucket),
			Key:     aws.String(key),
			IfMatch: aws.String(etag),
		})

	if err != nil {
		// If S3 returns a 404 Request Failure, mark the object as destroyed
		if awsErr, ok := err.(awserr.RequestFailure); ok && awsErr.StatusCode() == 404 {
			d.SetId("")
			log.Printf("[WARN] Error Reading Object (%s), object not found (HTTP status 404)", key)
			return nil
		}
		return err
	}

	d.Set("cache_control", resp.CacheControl)
	d.Set("content_disposition", resp.ContentDisposition)
	d.Set("content_encoding", resp.ContentEncoding)
	d.Set("content_language", resp.ContentLanguage)
	d.Set("content_type", resp.ContentType)

	log.Printf("[DEBUG] Reading S3 Bucket Object meta: %s", resp)
	return nil
}

func resourceAwsS3BucketObjectDelete(d *schema.ResourceData, meta interface{}) error {
	s3conn := meta.(*AWSClient).s3conn

	bucket := d.Get("bucket").(string)
	key := d.Get("key").(string)

	_, err := s3conn.DeleteObject(
		&s3.DeleteObjectInput{
			Bucket: aws.String(bucket),
			Key:    aws.String(key),
		})
	if err != nil {
		return fmt.Errorf("Error deleting S3 bucket object: %s", err)
	}
	return nil
}
<commit_msg>Because of the lack of Update, S3 Bucket Object needs to force new when changing the params<commit_after>package aws

import (
	"fmt"
	"log"
	"os"

	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/s3"
)

func resourceAwsS3BucketObject() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsS3BucketObjectPut,
		Read:   resourceAwsS3BucketObjectRead,
		Delete: resourceAwsS3BucketObjectDelete,

		Schema: map[string]*schema.Schema{
			"bucket": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"cache_control": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"content_disposition": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"content_encoding": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"content_language": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"content_type": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"key": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"source": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"etag": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}
}

func resourceAwsS3BucketObjectPut(d *schema.ResourceData, meta interface{}) error {
	s3conn := meta.(*AWSClient).s3conn

	bucket := d.Get("bucket").(string)
	key := d.Get("key").(string)

	source := d.Get("source").(string)
	file, err := os.Open(source)

	if err != nil {
		return fmt.Errorf("Error opening S3 bucket object source (%s): %s", source, err)
	}
	putInput := &s3.PutObjectInput{
		Bucket: aws.String(bucket),
		Key:    aws.String(key),
		Body:   file,
	}

	if v, ok := d.GetOk("cache_control"); ok {
		putInput.CacheControl = aws.String(v.(string))
	}

	if v, ok := d.GetOk("content_type"); ok {
		putInput.ContentType = aws.String(v.(string))
	}

	if v, ok := d.GetOk("content_encoding"); ok {
		putInput.ContentEncoding = aws.String(v.(string))
	}

	if v, ok := d.GetOk("content_language"); ok {
		putInput.ContentLanguage = aws.String(v.(string))
	}

	if v, ok := d.GetOk("content_disposition"); ok {
		putInput.ContentDisposition = aws.String(v.(string))
	}

	resp, err := s3conn.PutObject(putInput)

	if err != nil {
		return fmt.Errorf("Error putting object in S3 bucket (%s): %s", bucket, err)
	}

	d.Set("etag", resp.ETag)
	d.SetId(key)
	return nil
}

func resourceAwsS3BucketObjectRead(d *schema.ResourceData, meta interface{}) error {
	s3conn := meta.(*AWSClient).s3conn

	bucket := d.Get("bucket").(string)
	key := d.Get("key").(string)
	etag := d.Get("etag").(string)

	resp, err := s3conn.HeadObject(
		&s3.HeadObjectInput{
			Bucket:  aws.String(bucket),
			Key:     aws.String(key),
			IfMatch: aws.String(etag),
		})

	if err != nil {
		// If S3 returns a 404 Request Failure, mark the object as destroyed
		if awsErr, ok := err.(awserr.RequestFailure); ok && awsErr.StatusCode() == 404 {
			d.SetId("")
			log.Printf("[WARN] Error Reading Object (%s), object not found (HTTP status 404)", key)
			return nil
		}
		return err
	}

	d.Set("cache_control", resp.CacheControl)
	d.Set("content_disposition", resp.ContentDisposition)
	d.Set("content_encoding", resp.ContentEncoding)
	d.Set("content_language", resp.ContentLanguage)
	d.Set("content_type", resp.ContentType)

	log.Printf("[DEBUG] Reading S3 Bucket Object meta: %s", resp)
	return nil
}

func resourceAwsS3BucketObjectDelete(d *schema.ResourceData, meta interface{}) error {
	s3conn := meta.(*AWSClient).s3conn

	bucket := d.Get("bucket").(string)
	key := d.Get("key").(string)

	_, err := s3conn.DeleteObject(
		&s3.DeleteObjectInput{
			Bucket: aws.String(bucket),
			Key:    aws.String(key),
		})
	if err != nil {
		return fmt.Errorf("Error deleting S3 bucket object: %s", err)
	}
	return nil
}
<|endoftext|>


<commit_before>package openstack

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
)

func resourcePoolV2() *schema.Resource {
	return &schema.Resource{
		Create: resourcePoolV2Create,
		Read:   resourcePoolV2Read,
		Update: resourcePoolV2Update,
		Delete: resourcePoolV2Delete,

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(10 * time.Minute),
			Delete: schema.DefaultTimeout(10 * time.Minute),
		},

		Schema: map[string]*schema.Schema{
			"region": &schema.Schema{
				Type:        schema.TypeString,
				Required:    true,
				ForceNew:    true,
				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
			},

			"tenant_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"name": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"protocol": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if value != "TCP" && value != "HTTP" && value != "HTTPS" {
						errors = append(errors, fmt.Errorf(
							"Only 'TCP', 'HTTP', and 'HTTPS' are supported values for 'protocol'"))
					}
					return
				},
			},

			// One of loadbalancer_id or listener_id must be provided
			"loadbalancer_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			// One of loadbalancer_id or listener_id must be provided
			"listener_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"lb_method": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if value != "ROUND_ROBIN" && value != "LEAST_CONNECTIONS" && value != "SOURCE_IP" {
						errors = append(errors, fmt.Errorf(
							"Only 'ROUND_ROBIN', 'LEAST_CONNECTIONS', and 'SOURCE_IP' are supported values for 'lb_method'"))
					}
					return
				},
			},

			"persistence": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"type": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
								value := v.(string)
								if value != "SOURCE_IP" && value != "HTTP_COOKIE" && value != "APP_COOKIE" {
									errors = append(errors, fmt.Errorf(
										"Only 'SOURCE_IP', 'HTTP_COOKIE', and 'APP_COOKIE' are supported values for 'persistence'"))
								}
								return
							},
						},

						"cookie_name": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},
					},
				},
			},

			"admin_state_up": &schema.Schema{
				Type:     schema.TypeBool,
				Default:  true,
				Optional: true,
			},

			"id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}

func resourcePoolV2Create(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	adminStateUp := d.Get("admin_state_up").(bool)
	var persistence pools.SessionPersistence
	if p, ok := d.GetOk("persistence"); ok {
		pV := (p.([]interface{}))[0].(map[string]interface{})

		persistence = pools.SessionPersistence{
			Type:       pV["type"].(string),
			CookieName: pV["cookie_name"].(string),
		}
	}
	createOpts := pools.CreateOpts{
		TenantID:       d.Get("tenant_id").(string),
		Name:           d.Get("name").(string),
		Description:    d.Get("description").(string),
		Protocol:       pools.Protocol(d.Get("protocol").(string)),
		LoadbalancerID: d.Get("loadbalancer_id").(string),
		ListenerID:     d.Get("listener_id").(string),
		LBMethod:       pools.LBMethod(d.Get("lb_method").(string)),
		AdminStateUp:   &adminStateUp,
	}
	// Must omit if not set
	if persistence != (pools.SessionPersistence{}) {
		createOpts.Persistence = &persistence
	}

	log.Printf("[DEBUG] Create Options: %#v", createOpts)
	pool, err := pools.Create(networkingClient, createOpts).Extract()
	if err != nil {
		return fmt.Errorf("Error creating OpenStack LBaaSV2 pool: %s", err)
	}
	log.Printf("[INFO] pool ID: %s", pool.ID)

	log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 pool (%s) to become available.", pool.ID)

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"PENDING_CREATE"},
		Target:     []string{"ACTIVE"},
		Refresh:    waitForPoolActive(networkingClient, pool.ID),
		Timeout:    d.Timeout(schema.TimeoutCreate),
		Delay:      5 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return err
	}

	d.SetId(pool.ID)

	return resourcePoolV2Read(d, meta)
}

func resourcePoolV2Read(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	pool, err := pools.Get(networkingClient, d.Id()).Extract()
	if err != nil {
		return CheckDeleted(d, err, "LBV2 Pool")
	}

	log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 Pool %s: %+v", d.Id(), pool)

	d.Set("lb_method", pool.LBMethod)
	d.Set("protocol", pool.Protocol)
	d.Set("description", pool.Description)
	d.Set("tenant_id", pool.TenantID)
	d.Set("admin_state_up", pool.AdminStateUp)
	d.Set("name", pool.Name)
	d.Set("id", pool.ID)
	d.Set("persistence", pool.Persistence)

	return nil
}

func resourcePoolV2Update(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	var updateOpts pools.UpdateOpts
	if d.HasChange("lb_method") {
		updateOpts.LBMethod = pools.LBMethod(d.Get("lb_method").(string))
	}
	if d.HasChange("name") {
		updateOpts.Name = d.Get("name").(string)
	}
	if d.HasChange("description") {
		updateOpts.Description = d.Get("description").(string)
	}
	if d.HasChange("admin_state_up") {
		asu := d.Get("admin_state_up").(bool)
		updateOpts.AdminStateUp = &asu
	}

	log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Pool %s with options: %+v", d.Id(), updateOpts)

	_, err = pools.Update(networkingClient, d.Id(), updateOpts).Extract()
	if err != nil {
		return fmt.Errorf("Error updating OpenStack LBaaSV2 Pool: %s", err)
	}

	return resourcePoolV2Read(d, meta)
}

func resourcePoolV2Delete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"ACTIVE", "PENDING_DELETE"},
		Target:     []string{"DELETED"},
		Refresh:    waitForPoolDelete(networkingClient, d.Id()),
		Timeout:    d.Timeout(schema.TimeoutDelete),
		Delay:      5 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf("Error deleting OpenStack LBaaSV2 Pool: %s", err)
	}

	d.SetId("")
	return nil
}

func waitForPoolActive(networkingClient *gophercloud.ServiceClient, poolID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		pool, err := pools.Get(networkingClient, poolID).Extract()
		if err != nil {
			return nil, "", err
		}

		// The pool resource has no Status attribute, so a successful Get is the best we can do
		log.Printf("[DEBUG] OpenStack LBaaSV2 Pool: %+v", pool)
		return pool, "ACTIVE", nil
	}
}

func waitForPoolDelete(networkingClient *gophercloud.ServiceClient, poolID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Pool %s", poolID)

		pool, err := pools.Get(networkingClient, poolID).Extract()
		if err != nil {
			if _, ok := err.(gophercloud.ErrDefault404); ok {
				log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID)
				return pool, "DELETED", nil
			}
			return pool, "ACTIVE", err
		}

		log.Printf("[DEBUG] Openstack LBaaSV2 Pool: %+v", pool)
		err = pools.Delete(networkingClient, poolID).ExtractErr()
		if err != nil {
			if _, ok := err.(gophercloud.ErrDefault404); ok {
				log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID)
				return pool, "DELETED", nil
			}

			if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
				if errCode.Actual == 409 {
					log.Printf("[DEBUG] OpenStack LBaaSV2 Pool (%s) is still in use.", poolID)
					return pool, "ACTIVE", nil
				}
			}

			return pool, "ACTIVE", err
		}

		log.Printf("[DEBUG] OpenStack LBaaSV2 Pool %s still active.", poolID)
		return pool, "ACTIVE", nil
	}
}
<commit_msg>provider/openstack: 409 Response on Pool Create (#13074)<commit_after>package openstack

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
)

func resourcePoolV2() *schema.Resource {
	return &schema.Resource{
		Create: resourcePoolV2Create,
		Read:   resourcePoolV2Read,
		Update: resourcePoolV2Update,
		Delete: resourcePoolV2Delete,

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(10 * time.Minute),
			Delete: schema.DefaultTimeout(10 * time.Minute),
		},

		Schema: map[string]*schema.Schema{
			"region": &schema.Schema{
				Type:        schema.TypeString,
				Required:    true,
				ForceNew:    true,
				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
			},

			"tenant_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"name": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"protocol": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if value != "TCP" && value != "HTTP" && value != "HTTPS" {
						errors = append(errors, fmt.Errorf(
							"Only 'TCP', 'HTTP', and 'HTTPS' are supported values for 'protocol'"))
					}
					return
				},
			},

			// One of loadbalancer_id or listener_id must be provided
			"loadbalancer_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			// One of loadbalancer_id or listener_id must be provided
			"listener_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
			},

			"lb_method": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(string)
					if value != "ROUND_ROBIN" && value != "LEAST_CONNECTIONS" && value != "SOURCE_IP" {
						errors = append(errors, fmt.Errorf(
							"Only 'ROUND_ROBIN', 'LEAST_CONNECTIONS', and 'SOURCE_IP' are supported values for 'lb_method'"))
					}
					return
				},
			},

			"persistence": &schema.Schema{
				Type:     schema.TypeList,
				Optional: true,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"type": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
								value := v.(string)
								if value != "SOURCE_IP" && value != "HTTP_COOKIE" && value != "APP_COOKIE" {
									errors = append(errors, fmt.Errorf(
										"Only 'SOURCE_IP', 'HTTP_COOKIE', and 'APP_COOKIE' are supported values for 'persistence'"))
								}
								return
							},
						},

						"cookie_name": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},
					},
				},
			},

			"admin_state_up": &schema.Schema{
				Type:     schema.TypeBool,
				Default:  true,
				Optional: true,
			},

			"id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}

func resourcePoolV2Create(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	adminStateUp := d.Get("admin_state_up").(bool)
	var persistence pools.SessionPersistence
	if p, ok := d.GetOk("persistence"); ok {
		pV := (p.([]interface{}))[0].(map[string]interface{})

		persistence = pools.SessionPersistence{
			Type:       pV["type"].(string),
			CookieName: pV["cookie_name"].(string),
		}
	}
	createOpts := pools.CreateOpts{
		TenantID:       d.Get("tenant_id").(string),
		Name:           d.Get("name").(string),
		Description:    d.Get("description").(string),
		Protocol:       pools.Protocol(d.Get("protocol").(string)),
		LoadbalancerID: d.Get("loadbalancer_id").(string),
		ListenerID:     d.Get("listener_id").(string),
		LBMethod:       pools.LBMethod(d.Get("lb_method").(string)),
		AdminStateUp:   &adminStateUp,
	}
	// Must omit if not set
	if persistence != (pools.SessionPersistence{}) {
		createOpts.Persistence = &persistence
	}

	log.Printf("[DEBUG] Create Options: %#v", createOpts)

	var pool *pools.Pool
	err = resource.Retry(d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
		var err error
		log.Printf("[DEBUG] Attempting to create LBaaSV2 pool")
		pool, err = pools.Create(networkingClient, createOpts).Extract()
		if err != nil {
			switch errCode := err.(type) {
			case gophercloud.ErrDefault500:
				log.Printf("[DEBUG] OpenStack LBaaSV2 pool is still creating.")
				return resource.RetryableError(err)
			case gophercloud.ErrUnexpectedResponseCode:
				if errCode.Actual == 409 {
					log.Printf("[DEBUG] OpenStack LBaaSV2 pool is still creating.")
					return resource.RetryableError(err)
				}
			default:
				return resource.NonRetryableError(err)
			}
		}
		return nil
	})

	if err != nil {
		return fmt.Errorf("Error creating OpenStack LBaaSV2 pool: %s", err)
	}

	log.Printf("[INFO] pool ID: %s", pool.ID)

	log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 pool (%s) to become available.", pool.ID)

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"PENDING_CREATE"},
		Target:     []string{"ACTIVE"},
		Refresh:    waitForPoolActive(networkingClient, pool.ID),
		Timeout:    d.Timeout(schema.TimeoutCreate),
		Delay:      5 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return err
	}

	d.SetId(pool.ID)

	return resourcePoolV2Read(d, meta)
}

func resourcePoolV2Read(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	pool, err := pools.Get(networkingClient, d.Id()).Extract()
	if err != nil {
		return CheckDeleted(d, err, "LBV2 Pool")
	}

	log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 Pool %s: %+v", d.Id(), pool)

	d.Set("lb_method", pool.LBMethod)
	d.Set("protocol", pool.Protocol)
	d.Set("description", pool.Description)
	d.Set("tenant_id", pool.TenantID)
	d.Set("admin_state_up", pool.AdminStateUp)
	d.Set("name", pool.Name)
	d.Set("id", pool.ID)
	d.Set("persistence", pool.Persistence)

	return nil
}

func resourcePoolV2Update(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	var updateOpts pools.UpdateOpts
	if d.HasChange("lb_method") {
		updateOpts.LBMethod = pools.LBMethod(d.Get("lb_method").(string))
	}
	if d.HasChange("name") {
		updateOpts.Name = d.Get("name").(string)
	}
	if d.HasChange("description") {
		updateOpts.Description = d.Get("description").(string)
	}
	if d.HasChange("admin_state_up") {
		asu := d.Get("admin_state_up").(bool)
		updateOpts.AdminStateUp = &asu
	}

	log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Pool %s with options: %+v", d.Id(), updateOpts)

	_, err = pools.Update(networkingClient, d.Id(), updateOpts).Extract()
	if err != nil {
		return fmt.Errorf("Error updating OpenStack LBaaSV2 Pool: %s", err)
	}

	return resourcePoolV2Read(d, meta)
}

func resourcePoolV2Delete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"ACTIVE", "PENDING_DELETE"},
		Target:     []string{"DELETED"},
		Refresh:    waitForPoolDelete(networkingClient, d.Id()),
		Timeout:    d.Timeout(schema.TimeoutDelete),
		Delay:      5 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf("Error deleting OpenStack LBaaSV2 Pool: %s", err)
	}

	d.SetId("")
	return nil
}

func waitForPoolActive(networkingClient *gophercloud.ServiceClient, poolID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		pool, err := pools.Get(networkingClient, poolID).Extract()
		if err != nil {
			return nil, "", err
		}

		// The pool resource has no Status attribute, so a successful Get is the best we can do
		log.Printf("[DEBUG] OpenStack LBaaSV2 Pool: %+v", pool)
		return pool, "ACTIVE", nil
	}
}

func waitForPoolDelete(networkingClient *gophercloud.ServiceClient, poolID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Pool %s", poolID)

		pool, err := pools.Get(networkingClient, poolID).Extract()
		if err != nil {
			if _, ok := err.(gophercloud.ErrDefault404); ok {
				log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID)
				return pool, "DELETED", nil
			}
			return pool, "ACTIVE", err
		}

		log.Printf("[DEBUG] Openstack LBaaSV2 Pool: %+v", pool)
		err = pools.Delete(networkingClient, poolID).ExtractErr()
		if err != nil {
			if _, ok := err.(gophercloud.ErrDefault404); ok {
				log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Pool %s", poolID)
				return pool, "DELETED", nil
			}

			if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
				if errCode.Actual == 409 {
					log.Printf("[DEBUG] OpenStack LBaaSV2 Pool (%s) is still in use.", poolID)
					return pool, "ACTIVE", nil
				}
			}

			return pool, "ACTIVE", err
		}

		log.Printf("[DEBUG] OpenStack LBaaSV2 Pool %s still active.", poolID)
		return pool, "ACTIVE", nil
	}
}
<|endoftext|>


<commit_before>package crocker

import (
	"io"
	"os"
	. "polydawn.net/pogo/gosh"
	. "polydawn.net/hroot/util"
)

/*
	Refers to a Container process.
	The container has been started and has an ID.
	Methods on this struct can be used to inspect and manipulate the container.
*/
type Container struct {
	// reference to the daemon we launched this container with
	dock *Dock

	// id of this container
	id string
}

/*
	Launches a new Container in the given Dock.
	Punting on documentation while things are in flux; see command.go struct for details.
*/
func Launch(dock *Dock, image string, command []string, attach bool, privileged bool, startIn string, dns []string, mounts [][]string, ports [][]string, environment [][]string) *Container {
	dockRun := dock.Cmd()("run")

	//Where should docker write the new CID?
	CIDfilename := CreateCIDfile()
	dockRun = dockRun("-cidfile", CIDfilename)

	//Where should the container start?
	dockRun = dockRun("-w", startIn)

	//Is the docker in privleged (pwn ur box) mode?
	if privileged {
		dockRun = dockRun("-privileged")
	}

	//Custom DNS servers?
	for i := range dns {
		dockRun = dockRun("-dns", dns[i])
	}

	//What folders get mounted?
	for i := range mounts {
		dockRun = dockRun("-v", mounts[i][0] + ":" + mounts[i][1] + ":" + mounts[i][2])
	}

	for i := range ports {
		dockRun = dockRun("-p", ports[i][0] + ":" + ports[i][1])
	}

	//What environment variables are set?
	for i:= range environment {
		dockRun = dockRun("-e", environment[i][0] + "=" + environment[i][1])
	}

	//Are we attaching?
	if attach {
		dockRun = dockRun("-i", "-t")
	}

	//Add image name
	dockRun = dockRun(image)

	//What command should it run?
	for i := range command {
		dockRun = dockRun(command[i])
	}

	//Poll for the CID and run the docker
	dockRun()
	getCID := PollCid(CIDfilename)

	return &Container{
		dock: dock,
		id:   <-getCID,
	}
}

/*
	Waits for the container's main process to exit (i.e., wraps `docker wait`).
*/
func (c *Container) Wait() {
	//TOOD:FUTURE: consider if wait/attach should be just built-in when launching a container, and this method just wraps checks against a promise-pattern.
	c.dock.Cmd()("wait", c.id)()
}

/*
	Discards the container state and filesystem (i.e., wraps `docker rm`).

	After this method completes, many other inspection methods on this Container become
	invalid and will error, because the backing data has been, well, purged.

	This will error if called on a still-running container.
*/
func (c *Container) Purge() {
	c.dock.Cmd()("rm", c.id)()
}

/*
	Streams out a tar as produced by `docker export`.
*/
func (c *Container) Export(writer io.Writer) {
	c.dock.Cmd()("export", c.id)(Opts{Out: writer})()
	writer.(io.WriteCloser).Close() //... this might be fixed by updating gosh
}

/*
	Commits the container.
*/
func (c *Container) Commit(name, tag string) {
	c.dock.Cmd()("commit", c.id, name, tag)()
}

/*
	Convenience wrapper for Export(io.Writer) but writing to a file.
*/
func (c *Container) ExportToFilename(path string) {
	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil { ExitGently(err) }

	c.Export(out)
}
<commit_msg>Some docker flags are now double-dash<commit_after>package crocker

import (
	"io"
	"os"
	. "polydawn.net/pogo/gosh"
	. "polydawn.net/hroot/util"
)

/*
	Refers to a Container process.
	The container has been started and has an ID.
	Methods on this struct can be used to inspect and manipulate the container.
*/
type Container struct {
	// reference to the daemon we launched this container with
	dock *Dock

	// id of this container
	id string
}

/*
	Launches a new Container in the given Dock.
	Punting on documentation while things are in flux; see command.go struct for details.
*/
func Launch(dock *Dock, image string, command []string, attach bool, privileged bool, startIn string, dns []string, mounts [][]string, ports [][]string, environment [][]string) *Container {
	dockRun := dock.Cmd()("run")

	//Where should docker write the new CID?
	CIDfilename := CreateCIDfile()
	dockRun = dockRun("--cidfile", CIDfilename)

	//Where should the container start?
	dockRun = dockRun("-w", startIn)

	//Is the docker in privleged (pwn ur box) mode?
	if privileged {
		dockRun = dockRun("-privileged")
	}

	//Custom DNS servers?
	for i := range dns {
		dockRun = dockRun("--dns", dns[i])
	}

	//What folders get mounted?
	for i := range mounts {
		dockRun = dockRun("-v", mounts[i][0] + ":" + mounts[i][1] + ":" + mounts[i][2])
	}

	for i := range ports {
		dockRun = dockRun("-p", ports[i][0] + ":" + ports[i][1])
	}

	//What environment variables are set?
	for i:= range environment {
		dockRun = dockRun("-e", environment[i][0] + "=" + environment[i][1])
	}

	//Are we attaching?
	if attach {
		dockRun = dockRun("-i", "-t")
	}

	//Add image name
	dockRun = dockRun(image)

	//What command should it run?
	for i := range command {
		dockRun = dockRun(command[i])
	}

	//Poll for the CID and run the docker
	dockRun()
	getCID := PollCid(CIDfilename)

	return &Container{
		dock: dock,
		id:   <-getCID,
	}
}

/*
	Waits for the container's main process to exit (i.e., wraps `docker wait`).
*/
func (c *Container) Wait() {
	//TOOD:FUTURE: consider if wait/attach should be just built-in when launching a container, and this method just wraps checks against a promise-pattern.
	c.dock.Cmd()("wait", c.id)()
}

/*
	Discards the container state and filesystem (i.e., wraps `docker rm`).

	After this method completes, many other inspection methods on this Container become
	invalid and will error, because the backing data has been, well, purged.

	This will error if called on a still-running container.
*/
func (c *Container) Purge() {
	c.dock.Cmd()("rm", c.id)()
}

/*
	Streams out a tar as produced by `docker export`.
*/
func (c *Container) Export(writer io.Writer) {
	c.dock.Cmd()("export", c.id)(Opts{Out: writer})()
	writer.(io.WriteCloser).Close() //... this might be fixed by updating gosh
}

/*
	Commits the container.
*/
func (c *Container) Commit(name, tag string) {
	c.dock.Cmd()("commit", c.id, name, tag)()
}

/*
	Convenience wrapper for Export(io.Writer) but writing to a file.
*/
func (c *Container) ExportToFilename(path string) {
	out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil { ExitGently(err) }

	c.Export(out)
}
<|endoftext|>


<commit_before>package aws

import (
	"bytes"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/rds"
)

func resourceAwsDbParameterGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsDbParameterGroupCreate,
		Read:   resourceAwsDbParameterGroupRead,
		Update: resourceAwsDbParameterGroupUpdate,
		Delete: resourceAwsDbParameterGroupDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"name": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc:  validateDbParamGroupName,
			},
			"name_prefix": &schema.Schema{
				Type:         schema.TypeString,
				Optional:     true,
				Computed:     true,
				ForceNew:     true,
				ValidateFunc: validateDbParamGroupNamePrefix,
			},
			"family": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Default:  "Managed by Terraform",
			},
			"parameter": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				ForceNew: false,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"name": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"value": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"apply_method": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
							Default:  "immediate",
						},
					},
				},
				Set: resourceAwsDbParameterHash,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceAwsDbParameterGroupCreate(d *schema.ResourceData, meta interface{}) error {
	rdsconn := meta.(*AWSClient).rdsconn
	tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))

	var groupName string
	if v, ok := d.GetOk("name"); ok {
		groupName = v.(string)
	} else if v, ok := d.GetOk("name_prefix"); ok {
		groupName = resource.PrefixedUniqueId(v.(string))
	} else {
		groupName = resource.UniqueId()
	}
	d.Set("name", groupName)

	createOpts := rds.CreateDBParameterGroupInput{
		DBParameterGroupName:   aws.String(groupName),
		DBParameterGroupFamily: aws.String(d.Get("family").(string)),
		Description:            aws.String(d.Get("description").(string)),
		Tags:                   tags,
	}

	log.Printf("[DEBUG] Create DB Parameter Group: %#v", createOpts)
	_, err := rdsconn.CreateDBParameterGroup(&createOpts)
	if err != nil {
		return fmt.Errorf("Error creating DB Parameter Group: %s", err)
	}

	d.Partial(true)
	d.SetPartial("name")
	d.SetPartial("family")
	d.SetPartial("description")
	d.Partial(false)

	d.SetId(*createOpts.DBParameterGroupName)
	log.Printf("[INFO] DB Parameter Group ID: %s", d.Id())

	return resourceAwsDbParameterGroupUpdate(d, meta)
}

func resourceAwsDbParameterGroupRead(d *schema.ResourceData, meta interface{}) error {
	rdsconn := meta.(*AWSClient).rdsconn

	describeOpts := rds.DescribeDBParameterGroupsInput{
		DBParameterGroupName: aws.String(d.Id()),
	}

	describeResp, err := rdsconn.DescribeDBParameterGroups(&describeOpts)
	if err != nil {
		return err
	}

	if len(describeResp.DBParameterGroups) != 1 ||
		*describeResp.DBParameterGroups[0].DBParameterGroupName != d.Id() {
		return fmt.Errorf("Unable to find Parameter Group: %#v", describeResp.DBParameterGroups)
	}

	d.Set("name", describeResp.DBParameterGroups[0].DBParameterGroupName)
	d.Set("family", describeResp.DBParameterGroups[0].DBParameterGroupFamily)
	d.Set("description", describeResp.DBParameterGroups[0].Description)

	// Only include user customized parameters as there's hundreds of system/default ones
	describeParametersOpts := rds.DescribeDBParametersInput{
		DBParameterGroupName: aws.String(d.Id()),
		Source:               aws.String("user"),
	}

	describeParametersResp, err := rdsconn.DescribeDBParameters(&describeParametersOpts)
	if err != nil {
		return err
	}

	d.Set("parameter", flattenParameters(describeParametersResp.Parameters))

	paramGroup := describeResp.DBParameterGroups[0]
	arn, err := buildRDSPGARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region)
	if err != nil {
		name := "<empty>"
		if paramGroup.DBParameterGroupName != nil && *paramGroup.DBParameterGroupName != "" {
			name = *paramGroup.DBParameterGroupName
		}
		log.Printf("[DEBUG] Error building ARN for DB Parameter Group, not setting Tags for Param Group %s", name)
	} else {
		d.Set("arn", arn)
		resp, err := rdsconn.ListTagsForResource(&rds.ListTagsForResourceInput{
			ResourceName: aws.String(arn),
		})

		if err != nil {
			log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn)
		}

		var dt []*rds.Tag
		if len(resp.TagList) > 0 {
			dt = resp.TagList
		}
		d.Set("tags", tagsToMapRDS(dt))
	}

	return nil
}

func resourceAwsDbParameterGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	rdsconn := meta.(*AWSClient).rdsconn

	d.Partial(true)

	if d.HasChange("parameter") {
		o, n := d.GetChange("parameter")
		if o == nil {
			o = new(schema.Set)
		}
		if n == nil {
			n = new(schema.Set)
		}

		os := o.(*schema.Set)
		ns := n.(*schema.Set)

		// Expand the "parameter" set to aws-sdk-go compat []rds.Parameter
		parameters, err := expandParameters(ns.Difference(os).List())
		if err != nil {
			return err
		}

		if len(parameters) > 0 {
			// We can only modify 20 parameters at a time, so walk them until
			// we've got them all.
			maxParams := 20
			for parameters != nil {
				paramsToModify := make([]*rds.Parameter, 0)
				if len(parameters) <= maxParams {
					paramsToModify, parameters = parameters[:], nil
				} else {
					paramsToModify, parameters = parameters[:maxParams], parameters[maxParams:]
				}
				modifyOpts := rds.ModifyDBParameterGroupInput{
					DBParameterGroupName: aws.String(d.Get("name").(string)),
					Parameters:           paramsToModify,
				}

				log.Printf("[DEBUG] Modify DB Parameter Group: %s", modifyOpts)
				_, err = rdsconn.ModifyDBParameterGroup(&modifyOpts)
				if err != nil {
					return fmt.Errorf("Error modifying DB Parameter Group: %s", err)
				}
			}
			d.SetPartial("parameter")
		}
	}

	if arn, err := buildRDSPGARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region); err == nil {
		if err := setTagsRDS(rdsconn, d, arn); err != nil {
			return err
		} else {
			d.SetPartial("tags")
		}
	}

	d.Partial(false)

	return resourceAwsDbParameterGroupRead(d, meta)
}

func resourceAwsDbParameterGroupDelete(d *schema.ResourceData, meta interface{}) error {
	stateConf := &resource.StateChangeConf{
		Pending:    []string{"pending"},
		Target:     []string{"destroyed"},
		Refresh:    resourceAwsDbParameterGroupDeleteRefreshFunc(d, meta),
		Timeout:    3 * time.Minute,
		MinTimeout: 1 * time.Second,
	}
	_, err := stateConf.WaitForState()
	return err
}

func resourceAwsDbParameterGroupDeleteRefreshFunc(
	d *schema.ResourceData,
	meta interface{}) resource.StateRefreshFunc {
	rdsconn := meta.(*AWSClient).rdsconn

	return func() (interface{}, string, error) {

		deleteOpts := rds.DeleteDBParameterGroupInput{
			DBParameterGroupName: aws.String(d.Id()),
		}

		if _, err := rdsconn.DeleteDBParameterGroup(&deleteOpts); err != nil {
			rdserr, ok := err.(awserr.Error)
			if !ok {
				return d, "error", err
			}

			if rdserr.Code() != "DBParameterGroupNotFoundFault" {
				return d, "error", err
			}
		}

		return d, "destroyed", nil
	}
}

func resourceAwsDbParameterHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
	// Store the value as a lower case string, to match how we store them in flattenParameters
	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["value"].(string))))

	return hashcode.String(buf.String())
}

func buildRDSPGARN(identifier, partition, accountid, region string) (string, error) {
	if partition == "" {
		return "", fmt.Errorf("Unable to construct RDS ARN because of missing AWS partition")
	}
	if accountid == "" {
		return "", fmt.Errorf("Unable to construct RDS ARN because of missing AWS Account ID")
	}
	arn := fmt.Sprintf("arn:%s:rds:%s:%s:pg:%s", partition, region, accountid, identifier)
	return arn, nil

}
<commit_msg>provider/aws: Retry DB param grp delete on InvalidDBParameterGroupState (#15071)<commit_after>package aws

import (
	"bytes"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/rds"
)

func resourceAwsDbParameterGroup() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsDbParameterGroupCreate,
		Read:   resourceAwsDbParameterGroupRead,
		Update: resourceAwsDbParameterGroupUpdate,
		Delete: resourceAwsDbParameterGroupDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"arn": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"name": &schema.Schema{
				Type:          schema.TypeString,
				Optional:      true,
				Computed:      true,
				ForceNew:      true,
				ConflictsWith: []string{"name_prefix"},
				ValidateFunc:  validateDbParamGroupName,
			},
			"name_prefix": &schema.Schema{
				Type:         schema.TypeString,
				Optional:     true,
				Computed:     true,
				ForceNew:     true,
				ValidateFunc: validateDbParamGroupNamePrefix,
			},
			"family": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},
			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ForceNew: true,
				Default:  "Managed by Terraform",
			},
			"parameter": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				ForceNew: false,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"name": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"value": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"apply_method": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
							Default:  "immediate",
						},
					},
				},
				Set: resourceAwsDbParameterHash,
			},

			"tags": tagsSchema(),
		},
	}
}

func resourceAwsDbParameterGroupCreate(d *schema.ResourceData, meta interface{}) error {
	rdsconn := meta.(*AWSClient).rdsconn
	tags := tagsFromMapRDS(d.Get("tags").(map[string]interface{}))

	var groupName string
	if v, ok := d.GetOk("name"); ok {
		groupName = v.(string)
	} else if v, ok := d.GetOk("name_prefix"); ok {
		groupName = resource.PrefixedUniqueId(v.(string))
	} else {
		groupName = resource.UniqueId()
	}
	d.Set("name", groupName)

	createOpts := rds.CreateDBParameterGroupInput{
		DBParameterGroupName:   aws.String(groupName),
		DBParameterGroupFamily: aws.String(d.Get("family").(string)),
		Description:            aws.String(d.Get("description").(string)),
		Tags:                   tags,
	}

	log.Printf("[DEBUG] Create DB Parameter Group: %#v", createOpts)
	_, err := rdsconn.CreateDBParameterGroup(&createOpts)
	if err != nil {
		return fmt.Errorf("Error creating DB Parameter Group: %s", err)
	}

	d.Partial(true)
	d.SetPartial("name")
	d.SetPartial("family")
	d.SetPartial("description")
	d.Partial(false)

	d.SetId(*createOpts.DBParameterGroupName)
	log.Printf("[INFO] DB Parameter Group ID: %s", d.Id())

	return resourceAwsDbParameterGroupUpdate(d, meta)
}

func resourceAwsDbParameterGroupRead(d *schema.ResourceData, meta interface{}) error {
	rdsconn := meta.(*AWSClient).rdsconn

	describeOpts := rds.DescribeDBParameterGroupsInput{
		DBParameterGroupName: aws.String(d.Id()),
	}

	describeResp, err := rdsconn.DescribeDBParameterGroups(&describeOpts)
	if err != nil {
		return err
	}

	if len(describeResp.DBParameterGroups) != 1 ||
		*describeResp.DBParameterGroups[0].DBParameterGroupName != d.Id() {
		return fmt.Errorf("Unable to find Parameter Group: %#v", describeResp.DBParameterGroups)
	}

	d.Set("name", describeResp.DBParameterGroups[0].DBParameterGroupName)
	d.Set("family", describeResp.DBParameterGroups[0].DBParameterGroupFamily)
	d.Set("description", describeResp.DBParameterGroups[0].Description)

	// Only include user customized parameters as there's hundreds of system/default ones
	describeParametersOpts := rds.DescribeDBParametersInput{
		DBParameterGroupName: aws.String(d.Id()),
		Source:               aws.String("user"),
	}

	describeParametersResp, err := rdsconn.DescribeDBParameters(&describeParametersOpts)
	if err != nil {
		return err
	}

	d.Set("parameter", flattenParameters(describeParametersResp.Parameters))

	paramGroup := describeResp.DBParameterGroups[0]
	arn, err := buildRDSPGARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region)
	if err != nil {
		name := "<empty>"
		if paramGroup.DBParameterGroupName != nil && *paramGroup.DBParameterGroupName != "" {
			name = *paramGroup.DBParameterGroupName
		}
		log.Printf("[DEBUG] Error building ARN for DB Parameter Group, not setting Tags for Param Group %s", name)
	} else {
		d.Set("arn", arn)
		resp, err := rdsconn.ListTagsForResource(&rds.ListTagsForResourceInput{
			ResourceName: aws.String(arn),
		})

		if err != nil {
			log.Printf("[DEBUG] Error retrieving tags for ARN: %s", arn)
		}

		var dt []*rds.Tag
		if len(resp.TagList) > 0 {
			dt = resp.TagList
		}
		d.Set("tags", tagsToMapRDS(dt))
	}

	return nil
}

func resourceAwsDbParameterGroupUpdate(d *schema.ResourceData, meta interface{}) error {
	rdsconn := meta.(*AWSClient).rdsconn

	d.Partial(true)

	if d.HasChange("parameter") {
		o, n := d.GetChange("parameter")
		if o == nil {
			o = new(schema.Set)
		}
		if n == nil {
			n = new(schema.Set)
		}

		os := o.(*schema.Set)
		ns := n.(*schema.Set)

		// Expand the "parameter" set to aws-sdk-go compat []rds.Parameter
		parameters, err := expandParameters(ns.Difference(os).List())
		if err != nil {
			return err
		}

		if len(parameters) > 0 {
			// We can only modify 20 parameters at a time, so walk them until
			// we've got them all.
			maxParams := 20
			for parameters != nil {
				paramsToModify := make([]*rds.Parameter, 0)
				if len(parameters) <= maxParams {
					paramsToModify, parameters = parameters[:], nil
				} else {
					paramsToModify, parameters = parameters[:maxParams], parameters[maxParams:]
				}
				modifyOpts := rds.ModifyDBParameterGroupInput{
					DBParameterGroupName: aws.String(d.Get("name").(string)),
					Parameters:           paramsToModify,
				}

				log.Printf("[DEBUG] Modify DB Parameter Group: %s", modifyOpts)
				_, err = rdsconn.ModifyDBParameterGroup(&modifyOpts)
				if err != nil {
					return fmt.Errorf("Error modifying DB Parameter Group: %s", err)
				}
			}
			d.SetPartial("parameter")
		}
	}

	if arn, err := buildRDSPGARN(d.Id(), meta.(*AWSClient).partition, meta.(*AWSClient).accountid, meta.(*AWSClient).region); err == nil {
		if err := setTagsRDS(rdsconn, d, arn); err != nil {
			return err
		} else {
			d.SetPartial("tags")
		}
	}

	d.Partial(false)

	return resourceAwsDbParameterGroupRead(d, meta)
}

func resourceAwsDbParameterGroupDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).rdsconn
	return resource.Retry(3*time.Minute, func() *resource.RetryError {
		deleteOpts := rds.DeleteDBParameterGroupInput{
			DBParameterGroupName: aws.String(d.Id()),
		}

		_, err := conn.DeleteDBParameterGroup(&deleteOpts)
		if err != nil {
			awsErr, ok := err.(awserr.Error)
			if ok && awsErr.Code() == "DBParameterGroupNotFoundFault" {
				return resource.RetryableError(err)
			}
			if ok && awsErr.Code() == "InvalidDBParameterGroupState" {
				return resource.RetryableError(err)
			}
		}
		return resource.NonRetryableError(err)
	})
}

func resourceAwsDbParameterHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
	// Store the value as a lower case string, to match how we store them in flattenParameters
	buf.WriteString(fmt.Sprintf("%s-", strings.ToLower(m["value"].(string))))

	return hashcode.String(buf.String())
}

func buildRDSPGARN(identifier, partition, accountid, region string) (string, error) {
	if partition == "" {
		return "", fmt.Errorf("Unable to construct RDS ARN because of missing AWS partition")
	}
	if accountid == "" {
		return "", fmt.Errorf("Unable to construct RDS ARN because of missing AWS Account ID")
	}
	arn := fmt.Sprintf("arn:%s:rds:%s:%s:pg:%s", partition, region, accountid, identifier)
	return arn, nil

}
<|endoftext|>