<commit_before>package server

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"code.google.com/p/go.net/context"

	"github.com/livegrep/livegrep/client"
	"github.com/livegrep/livegrep/server/api"
	"github.com/livegrep/livegrep/server/log"
)

func replyJSON(ctx context.Context, w http.ResponseWriter, status int, obj interface{}) {
	w.WriteHeader(status)
	enc := json.NewEncoder(w)
	if err := enc.Encode(obj); err != nil {
		log.Printf(ctx, "writing http response, data=%s err=%s",
			asJSON{obj},
			err.Error())
	}
}

func writeError(ctx context.Context, w http.ResponseWriter, status int, code, message string) {
	log.Printf(ctx, "error status=%d code=%s message=%s",
		status, code, message)
	replyJSON(ctx, w, status, &api.ReplyError{Err: api.InnerError{Code: code, Message: message}})
}

func writeQueryError(ctx context.Context, w http.ResponseWriter, err error) {
	if qe, ok := err.(client.QueryError); ok {
		writeError(ctx, w, 400, "query_error", qe.Err)
	} else {
		writeError(ctx, w, 500, "internal_error",
			fmt.Sprintf("Talking to backend: %s", err.Error()))
	}
	return
}

func extractQuery(ctx context.Context, r *http.Request) client.Query {
	params := r.URL.Query()
	var query client.Query
	if q, ok := params["q"]; ok {
		query = ParseQuery(q[0])
		log.Printf(ctx, "parsing query q=%v out=%s", q[0], asJSON{query})
	}
	if line, ok := params["line"]; ok {
		query.Line = line[0]
	}
	if file, ok := params["file"]; ok {
		query.File = file[0]
	}
	if repo, ok := params["repo"]; ok {
		query.Repo = repo[0]
	}
	if fc, ok := params["fold_case"]; ok && fc[0] != "" {
		query.FoldCase = true
	}
	return query
}

const MaxRetries = 8

var (
	ErrTimedOut = errors.New("timed out talking to backend")
)

func (s *server) doSearch(ctx context.Context, backend *Backend, q *client.Query) (*api.ReplySearch, error) {
	var cl client.Client
	var search client.Search
	var err error

	select {
	case cl = <-backend.Clients:
	case <-ctx.Done():
		return nil, ErrTimedOut
	}
	defer backend.CheckIn(cl)

	search, err = cl.Query(q)
	if err != nil {
		log.Printf(ctx, "error talking to backend err=%s", err)
		return nil, err
	}

	reply := &api.ReplySearch{Results: make([]*client.Result, 0)}

	for r := range search.Results() {
		reply.Results = append(reply.Results, r)
	}

	reply.Info, err = search.Close()
	if err != nil {
		return nil, err
	}
	return reply, nil
}

func (s *server) ServeAPISearch(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	backendName := r.URL.Query().Get(":backend")
	var backend *Backend
	if backendName != "" {
		backend = s.bk[backendName]
		if backend == nil {
			writeError(ctx, w, 400, "bad_backend",
				fmt.Sprintf("Unknown backend: %s", backendName))
			return
		}
	} else {
		for _, backend = range s.bk {
			break
		}
	}

	q := extractQuery(ctx, r)

	if q.Line == "" {
		writeError(ctx, w, 400, "bad_query",
			"You must specify a 'line' regex.")
		return
	}

	var reply *api.ReplySearch
	var err error

	for tries := 0; tries < MaxRetries; tries++ {
		reply, err = s.doSearch(ctx, backend, &q)
		if err == nil {
			break
		}
		if err == ErrTimedOut {
			break
		}
		if _, ok := err.(client.QueryError); ok {
			break
		}
		log.Printf(ctx, "error querying try=%d err=%q", tries, err)
	}

	if err != nil {
		log.Printf(ctx, "error in search err=%s", err)
		writeQueryError(ctx, w, err)
		return
	}

	log.Printf(ctx,
		"responding success results=%d why=%s stats=%s",
		len(reply.Results),
		reply.Info.ExitReason,
		asJSON{reply.Info})

	replyJSON(ctx, w, 200, reply)
}
<commit_msg>%s -> %q<commit_after>package server

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"code.google.com/p/go.net/context"

	"github.com/livegrep/livegrep/client"
	"github.com/livegrep/livegrep/server/api"
	"github.com/livegrep/livegrep/server/log"
)

func replyJSON(ctx context.Context, w http.ResponseWriter, status int, obj interface{}) {
	w.WriteHeader(status)
	enc := json.NewEncoder(w)
	if err := enc.Encode(obj); err != nil {
		log.Printf(ctx, "writing http response, data=%s err=%q",
			asJSON{obj},
			err.Error())
	}
}

func writeError(ctx context.Context, w http.ResponseWriter, status int, code, message string) {
	log.Printf(ctx, "error status=%d code=%s message=%q",
		status, code, message)
	replyJSON(ctx, w, status, &api.ReplyError{Err: api.InnerError{Code: code, Message: message}})
}

func writeQueryError(ctx context.Context, w http.ResponseWriter, err error) {
	if qe, ok := err.(client.QueryError); ok {
		writeError(ctx, w, 400, "query_error", qe.Err)
	} else {
		writeError(ctx, w, 500, "internal_error",
			fmt.Sprintf("Talking to backend: %s", err.Error()))
	}
	return
}

func extractQuery(ctx context.Context, r *http.Request) client.Query {
	params := r.URL.Query()
	var query client.Query
	if q, ok := params["q"]; ok {
		query = ParseQuery(q[0])
		log.Printf(ctx, "parsing query q=%q out=%s", q[0], asJSON{query})
	}
	if line, ok := params["line"]; ok {
		query.Line = line[0]
	}
	if file, ok := params["file"]; ok {
		query.File = file[0]
	}
	if repo, ok := params["repo"]; ok {
		query.Repo = repo[0]
	}
	if fc, ok := params["fold_case"]; ok && fc[0] != "" {
		query.FoldCase = true
	}
	return query
}

const MaxRetries = 8

var (
	ErrTimedOut = errors.New("timed out talking to backend")
)

func (s *server) doSearch(ctx context.Context, backend *Backend, q *client.Query) (*api.ReplySearch, error) {
	var cl client.Client
	var search client.Search
	var err error

	select {
	case cl = <-backend.Clients:
	case <-ctx.Done():
		return nil, ErrTimedOut
	}
	defer backend.CheckIn(cl)

	search, err = cl.Query(q)
	if err != nil {
		log.Printf(ctx, "error talking to backend err=%s", err)
		return nil, err
	}

	reply := &api.ReplySearch{Results: make([]*client.Result, 0)}

	for r := range search.Results() {
		reply.Results = append(reply.Results, r)
	}

	reply.Info, err = search.Close()
	if err != nil {
		return nil, err
	}
	return reply, nil
}

func (s *server) ServeAPISearch(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	backendName := r.URL.Query().Get(":backend")
	var backend *Backend
	if backendName != "" {
		backend = s.bk[backendName]
		if backend == nil {
			writeError(ctx, w, 400, "bad_backend",
				fmt.Sprintf("Unknown backend: %s", backendName))
			return
		}
	} else {
		for _, backend = range s.bk {
			break
		}
	}

	q := extractQuery(ctx, r)

	if q.Line == "" {
		writeError(ctx, w, 400, "bad_query",
			"You must specify a 'line' regex.")
		return
	}

	var reply *api.ReplySearch
	var err error

	for tries := 0; tries < MaxRetries; tries++ {
		reply, err = s.doSearch(ctx, backend, &q)
		if err == nil {
			break
		}
		if err == ErrTimedOut {
			break
		}
		if _, ok := err.(client.QueryError); ok {
			break
		}
		log.Printf(ctx, "error querying try=%d err=%q", tries, err)
	}

	if err != nil {
		log.Printf(ctx, "error in search err=%s", err)
		writeQueryError(ctx, w, err)
		return
	}

	log.Printf(ctx,
		"responding success results=%d why=%s stats=%s",
		len(reply.Results),
		reply.Info.ExitReason,
		asJSON{reply.Info})

	replyJSON(ctx, w, 200, reply)
}
<|endoftext|>


<commit_before>package push

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("push with a manifest and an app name", func() {
	var (
		appName                   string
		randomHostName            string
		tempDir                   string
		pathToManifestWithNoRoute string
	)

	BeforeEach(func() {
		appName = helpers.NewAppName()
		domainName := helpers.DefaultSharedDomain()
		randomHostName = helpers.RandomName()

		var err error
		tempDir, err = ioutil.TempDir("", "no-route-flag-with-manifest-test")
		Expect(err).ToNot(HaveOccurred())
		pathToSetupManifest := filepath.Join(tempDir, "setup-manifest.yml")
		helpers.WriteManifest(pathToSetupManifest, map[string]interface{}{
			"applications": []map[string]interface{}{
				{
					"name": appName,
					"routes": []map[string]string{
						{"route": fmt.Sprintf("%s.%s", appName, domainName)},
					},
				},
			},
		})

		helpers.WithHelloWorldApp(func(dir string) {
			session := helpers.CustomCF(
				helpers.CFEnv{WorkingDirectory: dir},
				PushCommandName, appName,
				"-f", pathToSetupManifest,
				"--no-start")
			Eventually(session).Should(Exit(0))
		})

		pathToManifestWithNoRoute = filepath.Join(tempDir, "no-route-manifest.yml")
		helpers.WriteManifest(pathToManifestWithNoRoute, map[string]interface{}{
			"applications": []map[string]interface{}{
				{
					"name":     appName,
					"no-route": true,
					"routes": []map[string]string{
						{"route": fmt.Sprintf("%s.%s", randomHostName, domainName)},
					},
				},
			},
		})
	})

	AfterEach(func() {
		Expect(os.RemoveAll(tempDir)).ToNot(HaveOccurred())
	})

	It("pushes the app, doesnt map new routes, and removes old routes", func() {
		helpers.WithHelloWorldApp(func(dir string) {
			session := helpers.CustomCF(
				helpers.CFEnv{WorkingDirectory: dir},
				PushCommandName, appName,
				"-f", pathToManifestWithNoRoute,
				"--no-start")
			Eventually(session).Should(Exit(0))
		})

		session := helpers.CF("app", appName)
		Eventually(session).Should(Say(`(?m)routes:\s+$`))
		Eventually(session).Should(Exit(0))

	})

})
<commit_msg>Pend a test until the API changes it needs are finished<commit_after>package push

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("push with a manifest and an app name", func() {
	var (
		appName                   string
		randomHostName            string
		tempDir                   string
		pathToManifestWithNoRoute string
	)

	BeforeEach(func() {
		Skip("Waiting for no-route changes to be made on the API")
		appName = helpers.NewAppName()
		domainName := helpers.DefaultSharedDomain()
		randomHostName = helpers.RandomName()

		var err error
		tempDir, err = ioutil.TempDir("", "no-route-flag-with-manifest-test")
		Expect(err).ToNot(HaveOccurred())
		pathToSetupManifest := filepath.Join(tempDir, "setup-manifest.yml")
		helpers.WriteManifest(pathToSetupManifest, map[string]interface{}{
			"applications": []map[string]interface{}{
				{
					"name": appName,
					"routes": []map[string]string{
						{"route": fmt.Sprintf("%s.%s", appName, domainName)},
					},
				},
			},
		})

		helpers.WithHelloWorldApp(func(dir string) {
			session := helpers.CustomCF(
				helpers.CFEnv{WorkingDirectory: dir},
				PushCommandName, appName,
				"-f", pathToSetupManifest,
				"--no-start")
			Eventually(session).Should(Exit(0))
		})

		pathToManifestWithNoRoute = filepath.Join(tempDir, "no-route-manifest.yml")
		helpers.WriteManifest(pathToManifestWithNoRoute, map[string]interface{}{
			"applications": []map[string]interface{}{
				{
					"name":     appName,
					"no-route": true,
					"routes": []map[string]string{
						{"route": fmt.Sprintf("%s.%s", randomHostName, domainName)},
					},
				},
			},
		})
	})

	AfterEach(func() {
		Expect(os.RemoveAll(tempDir)).ToNot(HaveOccurred())
	})

	It("pushes the app, doesnt map new routes, and removes old routes", func() {
		helpers.WithHelloWorldApp(func(dir string) {
			session := helpers.CustomCF(
				helpers.CFEnv{WorkingDirectory: dir},
				PushCommandName, appName,
				"-f", pathToManifestWithNoRoute,
				"--no-start")
			Eventually(session).Should(Exit(0))
		})

		session := helpers.CF("app", appName)
		Eventually(session).Should(Say(`(?m)routes:\s+$`))
		Eventually(session).Should(Exit(0))

	})

})
<|endoftext|>


<commit_before>package main

import (
	"log"

	// /math.a conflicts with /runtime/internal/math.a sometimes. Make sure
	// that doesn't happen.
	"math"
)

func main() {
	log.Printf("MaxInt64: %d", math.MaxInt64)
}
<commit_msg>math.maxint32<commit_after>package main

import (
	"log"

	// /math.a conflicts with /runtime/internal/math.a sometimes. Make sure
	// that doesn't happen.
	"math"
)

func main() {
	log.Printf("MaxInt64: %d", math.MaxInt32)
}
<|endoftext|>


<commit_before>package configure

import (
	"fmt"
	"log"
	"runtime"
	"testing"

	"github.com/cubicdaiya/nginx-build/builder"
	"github.com/cubicdaiya/nginx-build/module3rd"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
)

type ConfiguregenTestSuite struct {
	suite.Suite
	builders   []builder.Builder
	modules3rd []module3rd.Module3rd
}

func (suite *ConfiguregenTestSuite) SetupTest() {
	suite.builders = make([]builder.Builder, builder.ComponentMax)

	suite.builders[builder.ComponentPcre] = builder.MakeBuilder(builder.ComponentPcre, builder.PcreVersion)
	suite.builders[builder.ComponentOpenSSL] = builder.MakeBuilder(builder.ComponentOpenSSL, builder.OpenSSLVersion)
	suite.builders[builder.ComponentZlib] = builder.MakeBuilder(builder.ComponentZlib, builder.ZlibVersion)

	modules3rdConf := "../config/modules.cfg.example"
	modules3rd, err := module3rd.Load(modules3rdConf)
	if err != nil {
		log.Fatalf("Failed to load %s\n", modules3rdConf)
	}
	suite.modules3rd = modules3rd
}

func (suite *ConfiguregenTestSuite) TestConfiguregenModule3rd() {
	configure_modules3rd := generateForModule3rd(suite.modules3rd)

	assert.Contains(suite.T(), configure_modules3rd, "-add-module=../echo-nginx-module")
	assert.Contains(suite.T(), configure_modules3rd, "-add-module=../headers-more-nginx-module")
	assert.Contains(suite.T(), configure_modules3rd, "-add-module=../ngx_devel_kit")
	assert.Contains(suite.T(), configure_modules3rd, "-add-dynamic-module=../ngx_small_light")
}

func (suite *ConfiguregenTestSuite) TestConfiguregenDefault() {
	var configureOptions Options
	configureScript := Generate("", []module3rd.Module3rd{}, []builder.StaticLibrary{}, configureOptions, "", false, 1)

	if runtime.GOOS == "darwin" {
		assert.Contains(suite.T(), configureScript, "--with-cc-opt=\"-Wno-deprecated-declarations\" \\")
	}
}

func (suite *ConfiguregenTestSuite) TestConfiguregenWithStaticLibraries() {
	var dependencies []builder.StaticLibrary
	dependencies = append(dependencies, builder.MakeStaticLibrary(&suite.builders[builder.ComponentPcre]))
	dependencies = append(dependencies, builder.MakeStaticLibrary(&suite.builders[builder.ComponentOpenSSL]))
	dependencies = append(dependencies, builder.MakeStaticLibrary(&suite.builders[builder.ComponentZlib]))
	var configureOptions Options
	configureScript := Generate("", []module3rd.Module3rd{}, dependencies, configureOptions, "", false, 1)

	assert.Contains(suite.T(), configureScript, "--with-http_ssl_module")
	assert.Contains(suite.T(), configureScript, fmt.Sprintf("--with-pcre=../pcre-%s \\\n", builder.PcreVersion))
	assert.Contains(suite.T(), configureScript, fmt.Sprintf("--with-openssl=../openssl-%s \\\n", builder.OpenSSLVersion))
	assert.Contains(suite.T(), configureScript, fmt.Sprintf("--with-zlib=../zlib-%s \\\n", builder.ZlibVersion))
}

func TestConfiguregenTestSuite(t *testing.T) {
	suite.Run(t, new(ConfiguregenTestSuite))
}
<commit_msg>configure: removed unneeded test.<commit_after>package configure

import (
	"fmt"
	"log"
	"testing"

	"github.com/cubicdaiya/nginx-build/builder"
	"github.com/cubicdaiya/nginx-build/module3rd"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
)

type ConfiguregenTestSuite struct {
	suite.Suite
	builders   []builder.Builder
	modules3rd []module3rd.Module3rd
}

func (suite *ConfiguregenTestSuite) SetupTest() {
	suite.builders = make([]builder.Builder, builder.ComponentMax)

	suite.builders[builder.ComponentPcre] = builder.MakeBuilder(builder.ComponentPcre, builder.PcreVersion)
	suite.builders[builder.ComponentOpenSSL] = builder.MakeBuilder(builder.ComponentOpenSSL, builder.OpenSSLVersion)
	suite.builders[builder.ComponentZlib] = builder.MakeBuilder(builder.ComponentZlib, builder.ZlibVersion)

	modules3rdConf := "../config/modules.cfg.example"
	modules3rd, err := module3rd.Load(modules3rdConf)
	if err != nil {
		log.Fatalf("Failed to load %s\n", modules3rdConf)
	}
	suite.modules3rd = modules3rd
}

func (suite *ConfiguregenTestSuite) TestConfiguregenModule3rd() {
	configure_modules3rd := generateForModule3rd(suite.modules3rd)

	assert.Contains(suite.T(), configure_modules3rd, "-add-module=../echo-nginx-module")
	assert.Contains(suite.T(), configure_modules3rd, "-add-module=../headers-more-nginx-module")
	assert.Contains(suite.T(), configure_modules3rd, "-add-module=../ngx_devel_kit")
	assert.Contains(suite.T(), configure_modules3rd, "-add-dynamic-module=../ngx_small_light")
}

func (suite *ConfiguregenTestSuite) TestConfiguregenWithStaticLibraries() {
	var dependencies []builder.StaticLibrary
	dependencies = append(dependencies, builder.MakeStaticLibrary(&suite.builders[builder.ComponentPcre]))
	dependencies = append(dependencies, builder.MakeStaticLibrary(&suite.builders[builder.ComponentOpenSSL]))
	dependencies = append(dependencies, builder.MakeStaticLibrary(&suite.builders[builder.ComponentZlib]))
	var configureOptions Options
	configureScript := Generate("", []module3rd.Module3rd{}, dependencies, configureOptions, "", false, 1)

	assert.Contains(suite.T(), configureScript, "--with-http_ssl_module")
	assert.Contains(suite.T(), configureScript, fmt.Sprintf("--with-pcre=../pcre-%s \\\n", builder.PcreVersion))
	assert.Contains(suite.T(), configureScript, fmt.Sprintf("--with-openssl=../openssl-%s \\\n", builder.OpenSSLVersion))
	assert.Contains(suite.T(), configureScript, fmt.Sprintf("--with-zlib=../zlib-%s \\\n", builder.ZlibVersion))
}

func TestConfiguregenTestSuite(t *testing.T) {
	suite.Run(t, new(ConfiguregenTestSuite))
}
<|endoftext|>


<commit_before>// Package mongo is a REST Layer resource storage handler for MongoDB using mgo
package mongo

import (
	"context"
	"time"

	"github.com/rs/rest-layer/resource"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

// mongoItem is a bson representation of a resource.Item
type mongoItem struct {
	ID      interface{}            `bson:"_id"`
	ETag    string                 `bson:"_etag"`
	Updated time.Time              `bson:"_updated"`
	Payload map[string]interface{} `bson:",inline"`
}

// newMongoItem converts a resource.Item into a mongoItem
func newMongoItem(i *resource.Item) *mongoItem {
	// Filter out id from the payload so we don't store it twice
	p := map[string]interface{}{}
	for k, v := range i.Payload {
		if k != "id" {
			p[k] = v
		}
	}
	return &mongoItem{
		ID:      i.ID,
		ETag:    i.ETag,
		Updated: i.Updated,
		Payload: p,
	}
}

// newItem converts a back mongoItem into a resource.Item
func newItem(i *mongoItem) *resource.Item {
	// Add the id back (we use the same map hoping the mongoItem won't be stored back)
	i.Payload["id"] = i.ID
	return &resource.Item{
		ID:      i.ID,
		ETag:    i.ETag,
		Updated: i.Updated,
		Payload: i.Payload,
	}
}

// Handler handles resource storage in a MongoDB collection.
type Handler func(ctx context.Context) (*mgo.Collection, error)

// NewHandler creates an new mongo handler
func NewHandler(s *mgo.Session, db, collection string) Handler {
	return func(ctx context.Context) (*mgo.Collection, error) {
		// With mgo, session.Copy() pulls a connection from the connection pool
		s := s.Copy()
		return s.DB(db).C(collection), nil
	}
}

// C returns the mongo collection managed by this storage handler
// from a Copy() of the mgo session.
func (m Handler) c(ctx context.Context) (*mgo.Collection, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	c, err := m(ctx)
	if err != nil {
		return nil, err
	}
	// Ensure safe mode is enabled in order to get errors
	c.Database.Session.EnsureSafe(&mgo.Safe{})
	// Set a timeout to match the context deadline if any
	if deadline, ok := ctx.Deadline(); ok {
		timeout := deadline.Sub(time.Now())
		if timeout <= 0 {
			timeout = 0
		}
		c.Database.Session.SetSocketTimeout(timeout)
		c.Database.Session.SetSyncTimeout(timeout)
	}
	return c, nil
}

// close returns a mgo.Collection's session to the connection pool.
func (m Handler) close(c *mgo.Collection) {
	c.Database.Session.Close()
}

// Insert inserts new items in the mongo collection
func (m Handler) Insert(ctx context.Context, items []*resource.Item) error {
	mItems := make([]interface{}, len(items))
	for i, item := range items {
		mItems[i] = newMongoItem(item)
	}
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Insert(mItems...)
	if mgo.IsDup(err) {
		// Duplicate ID key
		err = resource.ErrConflict
	}
	if ctx.Err() != nil {
		return ctx.Err()
	}
	return err
}

// Update replace an item by a new one in the mongo collection
func (m Handler) Update(ctx context.Context, item *resource.Item, original *resource.Item) error {
	mItem := newMongoItem(item)
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Update(bson.M{"_id": original.ID, "_etag": original.ETag}, mItem)
	if err == mgo.ErrNotFound {
		// Determine if the item is not found or if the item is found but etag missmatch
		var count int
		count, err = c.FindId(original.ID).Count()
		if err != nil {
			// The find returned an unexpected err, just forward it with no mapping
		} else if count == 0 {
			err = resource.ErrNotFound
		} else if ctx.Err() != nil {
			err = ctx.Err()
		} else {
			// If the item were found, it means that its etag didn't match
			err = resource.ErrConflict
		}
	}
	return err
}

// Delete deletes an item from the mongo collection
func (m Handler) Delete(ctx context.Context, item *resource.Item) error {
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Remove(bson.M{"_id": item.ID, "_etag": item.ETag})
	if err == mgo.ErrNotFound {
		// Determine if the item is not found or if the item is found but etag missmatch
		var count int
		count, err = c.FindId(item.ID).Count()
		if err != nil {
			// The find returned an unexpected err, just forward it with no mapping
		} else if count == 0 {
			err = resource.ErrNotFound
		} else if ctx.Err() != nil {
			err = ctx.Err()
		} else {
			// If the item were found, it means that its etag didn't match
			err = resource.ErrConflict
		}
	}
	return err
}

// Clear clears all items from the mongo collection matching the lookup
func (m Handler) Clear(ctx context.Context, lookup *resource.Lookup) (int, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return 0, err
	}
	c, err := m.c(ctx)
	if err != nil {
		return 0, err
	}
	defer m.close(c)
	info, err := c.RemoveAll(q)
	if err != nil {
		return 0, err
	}
	if ctx.Err() != nil {
		return 0, ctx.Err()
	}
	return info.Removed, nil
}

// Find items from the mongo collection matching the provided lookup
func (m Handler) Find(ctx context.Context, lookup *resource.Lookup, offset, limit int) (*resource.ItemList, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return nil, err
	}
	s := getSort(lookup)
	c, err := m.c(ctx)
	if err != nil {
		return nil, err
	}
	defer m.close(c)
	var mItem mongoItem
	query := c.Find(q).Sort(s...)

	if offset > 0 {
		query.Skip(offset)
	}
	if limit >= 0 {
		query.Limit(limit)
	}
	// Apply context deadline if any
	if dl, ok := ctx.Deadline(); ok {
		dur := dl.Sub(time.Now())
		if dur < 0 {
			dur = 0
		}
		query.SetMaxTime(dur)
	}
	// Perform request
	iter := query.Iter()
	// Total is set to -1 because we have no easy way with Mongodb to to compute this value
	// without performing two requests.
	list := &resource.ItemList{Total: -1, Items: []*resource.Item{}}
	for iter.Next(&mItem) {
		// Check if context is still ok before to continue
		if err = ctx.Err(); err != nil {
			// TODO bench this as net/context is using mutex under the hood
			iter.Close()
			return nil, err
		}
		list.Items = append(list.Items, newItem(&mItem))
	}
	if err := iter.Close(); err != nil {
		return nil, err
	}
	// If the number of returned elements is lower than requested limit, or not
	// limit is requested, we can deduce the total number of element for free.
	if limit == -1 || len(list.Items) < limit {
		list.Total = offset + len(list.Items)
	}
	return list, err
}

// Count counts the number items matching the lookup filter
func (m Handler) Count(ctx context.Context, lookup *resource.Lookup) (int, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return -1, err
	}
	c, err := m.c(ctx)
	if err != nil {
		return -1, err
	}
	defer m.close(c)
	query := c.Find(q)
	// Apply context deadline if any
	if dl, ok := ctx.Deadline(); ok {
		dur := dl.Sub(time.Now())
		if dur < 0 {
			dur = 0
		}
		query.SetMaxTime(dur)
	}
	return query.Count()
}
<commit_msg>Copy() only in c method.<commit_after>// Package mongo is a REST Layer resource storage handler for MongoDB using mgo
package mongo

import (
	"context"
	"time"

	"github.com/rs/rest-layer/resource"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

// mongoItem is a bson representation of a resource.Item
type mongoItem struct {
	ID      interface{}            `bson:"_id"`
	ETag    string                 `bson:"_etag"`
	Updated time.Time              `bson:"_updated"`
	Payload map[string]interface{} `bson:",inline"`
}

// newMongoItem converts a resource.Item into a mongoItem
func newMongoItem(i *resource.Item) *mongoItem {
	// Filter out id from the payload so we don't store it twice
	p := map[string]interface{}{}
	for k, v := range i.Payload {
		if k != "id" {
			p[k] = v
		}
	}
	return &mongoItem{
		ID:      i.ID,
		ETag:    i.ETag,
		Updated: i.Updated,
		Payload: p,
	}
}

// newItem converts a back mongoItem into a resource.Item
func newItem(i *mongoItem) *resource.Item {
	// Add the id back (we use the same map hoping the mongoItem won't be stored back)
	i.Payload["id"] = i.ID
	return &resource.Item{
		ID:      i.ID,
		ETag:    i.ETag,
		Updated: i.Updated,
		Payload: i.Payload,
	}
}

// Handler handles resource storage in a MongoDB collection.
type Handler func(ctx context.Context) (*mgo.Collection, error)

// NewHandler creates an new mongo handler
func NewHandler(s *mgo.Session, db, collection string) Handler {
	return func(ctx context.Context) (*mgo.Collection, error) {
		// With mgo, session.Copy() pulls a connection from the connection pool
		return s.DB(db).C(collection), nil
	}
}

// C returns the mongo collection managed by this storage handler
// from a Copy() of the mgo session.
func (m Handler) c(ctx context.Context) (*mgo.Collection, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	c, err := m(ctx)
	if err != nil {
		return nil, err
	}
	// Ensure safe mode is enabled in order to get errors
	c.Database.Session.Copy().EnsureSafe(&mgo.Safe{})
	// Set a timeout to match the context deadline if any
	if deadline, ok := ctx.Deadline(); ok {
		timeout := deadline.Sub(time.Now())
		if timeout <= 0 {
			timeout = 0
		}
		c.Database.Session.SetSocketTimeout(timeout)
		c.Database.Session.SetSyncTimeout(timeout)
	}
	return c, nil
}

// close returns a mgo.Collection's session to the connection pool.
func (m Handler) close(c *mgo.Collection) {
	c.Database.Session.Close()
}

// Insert inserts new items in the mongo collection
func (m Handler) Insert(ctx context.Context, items []*resource.Item) error {
	mItems := make([]interface{}, len(items))
	for i, item := range items {
		mItems[i] = newMongoItem(item)
	}
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Insert(mItems...)
	if mgo.IsDup(err) {
		// Duplicate ID key
		err = resource.ErrConflict
	}
	if ctx.Err() != nil {
		return ctx.Err()
	}
	return err
}

// Update replace an item by a new one in the mongo collection
func (m Handler) Update(ctx context.Context, item *resource.Item, original *resource.Item) error {
	mItem := newMongoItem(item)
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Update(bson.M{"_id": original.ID, "_etag": original.ETag}, mItem)
	if err == mgo.ErrNotFound {
		// Determine if the item is not found or if the item is found but etag missmatch
		var count int
		count, err = c.FindId(original.ID).Count()
		if err != nil {
			// The find returned an unexpected err, just forward it with no mapping
		} else if count == 0 {
			err = resource.ErrNotFound
		} else if ctx.Err() != nil {
			err = ctx.Err()
		} else {
			// If the item were found, it means that its etag didn't match
			err = resource.ErrConflict
		}
	}
	return err
}

// Delete deletes an item from the mongo collection
func (m Handler) Delete(ctx context.Context, item *resource.Item) error {
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Remove(bson.M{"_id": item.ID, "_etag": item.ETag})
	if err == mgo.ErrNotFound {
		// Determine if the item is not found or if the item is found but etag missmatch
		var count int
		count, err = c.FindId(item.ID).Count()
		if err != nil {
			// The find returned an unexpected err, just forward it with no mapping
		} else if count == 0 {
			err = resource.ErrNotFound
		} else if ctx.Err() != nil {
			err = ctx.Err()
		} else {
			// If the item were found, it means that its etag didn't match
			err = resource.ErrConflict
		}
	}
	return err
}

// Clear clears all items from the mongo collection matching the lookup
func (m Handler) Clear(ctx context.Context, lookup *resource.Lookup) (int, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return 0, err
	}
	c, err := m.c(ctx)
	if err != nil {
		return 0, err
	}
	defer m.close(c)
	info, err := c.RemoveAll(q)
	if err != nil {
		return 0, err
	}
	if ctx.Err() != nil {
		return 0, ctx.Err()
	}
	return info.Removed, nil
}

// Find items from the mongo collection matching the provided lookup
func (m Handler) Find(ctx context.Context, lookup *resource.Lookup, offset, limit int) (*resource.ItemList, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return nil, err
	}
	s := getSort(lookup)
	c, err := m.c(ctx)
	if err != nil {
		return nil, err
	}
	defer m.close(c)
	var mItem mongoItem
	query := c.Find(q).Sort(s...)

	if offset > 0 {
		query.Skip(offset)
	}
	if limit >= 0 {
		query.Limit(limit)
	}
	// Apply context deadline if any
	if dl, ok := ctx.Deadline(); ok {
		dur := dl.Sub(time.Now())
		if dur < 0 {
			dur = 0
		}
		query.SetMaxTime(dur)
	}
	// Perform request
	iter := query.Iter()
	// Total is set to -1 because we have no easy way with Mongodb to to compute this value
	// without performing two requests.
	list := &resource.ItemList{Total: -1, Items: []*resource.Item{}}
	for iter.Next(&mItem) {
		// Check if context is still ok before to continue
		if err = ctx.Err(); err != nil {
			// TODO bench this as net/context is using mutex under the hood
			iter.Close()
			return nil, err
		}
		list.Items = append(list.Items, newItem(&mItem))
	}
	if err := iter.Close(); err != nil {
		return nil, err
	}
	// If the number of returned elements is lower than requested limit, or not
	// limit is requested, we can deduce the total number of element for free.
	if limit == -1 || len(list.Items) < limit {
		list.Total = offset + len(list.Items)
	}
	return list, err
}

// Count counts the number items matching the lookup filter
func (m Handler) Count(ctx context.Context, lookup *resource.Lookup) (int, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return -1, err
	}
	c, err := m.c(ctx)
	if err != nil {
		return -1, err
	}
	defer m.close(c)
	query := c.Find(q)
	// Apply context deadline if any
	if dl, ok := ctx.Deadline(); ok {
		dur := dl.Sub(time.Now())
		if dur < 0 {
			dur = 0
		}
		query.SetMaxTime(dur)
	}
	return query.Count()
}
<|endoftext|>


<commit_before>// Copyright 2017 ibelie, Chen Jie, Joungtao. All rights reserved.
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.

package typescript

import (
	"log"
	"os"
	"path"
	"reflect"

	"encoding/json"
	"os/exec"
)

var JS_PATH = path.Join(os.Getenv("GOPATH"), "src", reflect.TypeOf(PackageStr{}).PkgPath(), "extract.js")

type TypeStr struct {
	Simple string
	List   *TypeStr
	Key    *TypeStr
	Value  *TypeStr
}

type FieldStr struct {
	Name     string
	Document string
	Type     *TypeStr
}

type MethodStr struct {
	Name     string
	Document string
	Result   *TypeStr
	Params   []*TypeStr
}

type ObjectStr struct {
	Name    string
	Parents []*TypeStr
	Fields  []*FieldStr
	Methods []*MethodStr
}

type PackageStr struct {
	Files   []string
	Objects []*ObjectStr
}

func Extract(file string) (pkg *PackageStr) {
	output, err := exec.Command("node", JS_PATH, file).CombinedOutput()
	if err != nil {
		log.Fatalf("[Typescript] Cannot extract: %s\n>>>> %v", string(output))
	} else if err = json.Unmarshal(output, &pkg); err != nil {
		log.Fatalf("[Typescript] Cannot unmarshal objects:\n>>>> %v", err)
	}
	return
}
<commit_msg>fix log error<commit_after>// Copyright 2017 ibelie, Chen Jie, Joungtao. All rights reserved.
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.

package typescript

import (
	"log"
	"os"
	"path"
	"reflect"

	"encoding/json"
	"os/exec"
)

var JS_PATH = path.Join(os.Getenv("GOPATH"), "src", reflect.TypeOf(PackageStr{}).PkgPath(), "extract.js")

type TypeStr struct {
	Simple string
	List   *TypeStr
	Key    *TypeStr
	Value  *TypeStr
}

type FieldStr struct {
	Name     string
	Document string
	Type     *TypeStr
}

type MethodStr struct {
	Name     string
	Document string
	Result   *TypeStr
	Params   []*TypeStr
}

type ObjectStr struct {
	Name    string
	Parents []*TypeStr
	Fields  []*FieldStr
	Methods []*MethodStr
}

type PackageStr struct {
	Files   []string
	Objects []*ObjectStr
}

func Extract(file string) (pkg *PackageStr) {
	output, err := exec.Command("node", JS_PATH, file).CombinedOutput()
	if err != nil {
		log.Fatalf("[Typescript] Cannot extract: %s\n>>>> %v", string(output), err)
	} else if err = json.Unmarshal(output, &pkg); err != nil {
		log.Fatalf("[Typescript] Cannot unmarshal objects:\n>>>> %v", err)
	}
	return
}
<|endoftext|>


<commit_before>/*
	The mongo package is a very simple wrapper around the labix.org/v2/mgo
	package. It's purpose is to allow you to do CRUD operations with very
	little code. It's not exhaustive and not meant to do everything for you.
*/
package mongo

import (
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"errors"
	"fmt"
	"reflect"
	"time"
)

var (
	mgoSession *mgo.Session
	servers    string
	database   string
	NoPtr      = errors.New("You must pass in a pointer")
)

// Set the mongo servers and the database
func SetServers(servers, db string) error {
	var err error

	database = db

	mgoSession, err = mgo.Dial(servers)
	return err
}

// Insert one or more structs. Must pass in a pointer to a struct. The struct must
// contain an Id field of type bson.ObjectId with a tag of `bson:"_id"`.
func Insert(records ...interface{}) error {
	for _, rec := range records {
		if !isPtr(rec) {
			return NoPtr
		}

		if err := addNewFields(rec); err != nil {
			return err
		}

		s, err := GetSession()
		if err != nil {
			return err
		}
		defer s.Close()

		coll := GetColl(s, typeName(rec))
		err = coll.Insert(rec)
		if err != nil {
			return err
		}
	}

	return nil
}

// Find one or more records. If a single struct is passed in we'll return one record.
// If a slice is passed in all records will be returned. Must pass in a pointer to a
// struct or slice of structs.
func Find(i interface{}, q bson.M) error {
	if !isPtr(i) {
		return NoPtr
	}

	s, err := GetSession()
	if err != nil {
		return err
	}
	defer s.Close()

	coll := GetColl(s, typeName(i))

	query := coll.Find(q)

	if isSlice(reflect.TypeOf(i)) {
		err = query.All(i)
	} else {
		err = query.One(i)
	}
	return err
}

// Find a single record by id. Must pass a pointer to a struct.
func FindById(i interface{}, id string) error {
	return Find(i, bson.M{"_id": bson.ObjectIdHex(id)})
}

// Updates a record. Uses the Id to identify the record to update. Must pass in a pointer
// to a struct.
func Update(i interface{}) error {
	if !isPtr(i) {
		return NoPtr
	}

	err := addCurrentDateTime(i, "UpdatedAt")
	if err != nil {
		return err
	}

	s, err := GetSession()
	if err != nil {
		return err
	}
	defer s.Close()

	id, err := getObjIdFromStruct(i)
	if err != nil {
		return err
	}

	return GetColl(s, typeName(i)).Update(bson.M{"_id": id}, i)
}

// Deletes a record. Uses the Id to identify the record to delete. Must pass in a pointer
// to a struct.
func Delete(i interface{}) error {
	if !isPtr(i) {
		return NoPtr
	}

	s, err := GetSession()
	if err != nil {
		return err
	}
	defer s.Close()

	id, err := getObjIdFromStruct(i)
	if err != nil {
		return err
	}

	return GetColl(s, typeName(i)).RemoveId(id)
}

// Returns a Mongo session. You must call Session.Close() when you're done.
func GetSession() (*mgo.Session, error) {
	var err error

	if mgoSession == nil {
		mgoSession, err = mgo.Dial(servers)
		if err != nil {
			return nil, err
		}
	}

	return mgoSession.Clone(), nil
}

// We pass in the session because that is a clone of the original and the
// caller will need to close it when finished.
func GetColl(session *mgo.Session, coll string) *mgo.Collection {
	return session.DB(database).C(coll)
}

func getObjIdFromStruct(i interface{}) (bson.ObjectId, error) {
	v := reflect.ValueOf(i)

	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return bson.ObjectId(""), errors.New("Can't delete record. Type must be a struct.")
	}

	f := v.FieldByName("Id")
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	return f.Interface().(bson.ObjectId), nil
}

func isPtr(i interface{}) bool {
	return reflect.ValueOf(i).Kind() == reflect.Ptr
}

func typeName(i interface{}) string {
	t := reflect.TypeOf(i)

	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if isSlice(t) {
		t = t.Elem()

		if t.Kind() == reflect.Ptr {
			t = t.Elem()
		}
	}

	return t.Name()
}

// returns true if the interface is a slice
func isSlice(t reflect.Type) bool {
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	return t.Kind() == reflect.Slice
}

func addNewFields(i interface{}) error {
	err := addId(i)
	if err != nil {
		return err
	}

	if err := addCurrentDateTime(i, "CreatedAt"); err != nil {
		return err
	}

	return addCurrentDateTime(i, "UpdatedAt")
}

func addCurrentDateTime(i interface{}, name string) error {
	if !hasStructField(i, name) {
		return nil
	}

	now := time.Now()

	v := reflect.ValueOf(i)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	f := v.FieldByName(name)
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	if reflect.TypeOf(now) != f.Type() {
		return fmt.Errorf("%v must be time.Time type.", name)
	}

	if !f.CanSet() {
		return fmt.Errorf("Couldn't set time for field: %v", name)
	}

	f.Set(reflect.ValueOf(now))

	return nil
}

func hasStructField(i interface{}, field string) bool {
	t := reflect.TypeOf(i)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if t.Kind() != reflect.Struct {
		return false
	}

	_, found := t.FieldByName(field)
	return found
}

func addId(i interface{}) error {
	v := reflect.ValueOf(i)

	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return errors.New("Record must be a struct")
	}

	f := v.FieldByName("Id")
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	if f.Kind() == reflect.String {
		if !f.Interface().(bson.ObjectId).Valid() {
			id := reflect.ValueOf(bson.NewObjectId())
			f.Set(id)
		}
	}

	return nil
}
<commit_msg>Add ability to use Id as a string type.<commit_after>/*
	The mongo package is a very simple wrapper around the labix.org/v2/mgo
	package. It's purpose is to allow you to do CRUD operations with very
	little code. It's not exhaustive and not meant to do everything for you.
*/
package mongo

import (
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"

	"errors"
	"fmt"
	"reflect"
	"time"
)

var (
	mgoSession *mgo.Session
	servers    string
	database   string
	NoPtr      = errors.New("You must pass in a pointer")
)

// Set the mongo servers and the database
func SetServers(servers, db string) error {
	var err error

	database = db

	mgoSession, err = mgo.Dial(servers)
	return err
}

// Insert one or more structs. Must pass in a pointer to a struct. The struct must
// contain an Id field of type bson.ObjectId with a tag of `bson:"_id"`.
func Insert(records ...interface{}) error {
	for _, rec := range records {
		if !isPtr(rec) {
			return NoPtr
		}

		if err := addNewFields(rec); err != nil {
			return err
		}

		s, err := GetSession()
		if err != nil {
			return err
		}
		defer s.Close()

		coll := GetColl(s, typeName(rec))
		err = coll.Insert(rec)
		if err != nil {
			return err
		}
	}

	return nil
}

// Find one or more records. If a single struct is passed in we'll return one record.
// If a slice is passed in all records will be returned. Must pass in a pointer to a
// struct or slice of structs.
func Find(i interface{}, q bson.M) error {
	if !isPtr(i) {
		return NoPtr
	}

	s, err := GetSession()
	if err != nil {
		return err
	}
	defer s.Close()

	coll := GetColl(s, typeName(i))

	query := coll.Find(q)

	if isSlice(reflect.TypeOf(i)) {
		err = query.All(i)
	} else {
		err = query.One(i)
	}
	return err
}

// Find a single record by id. Must pass a pointer to a struct.
func FindById(i interface{}, id string) error {
	return Find(i, bson.M{"_id": bson.ObjectIdHex(id)})
}

// Updates a record. Uses the Id to identify the record to update. Must pass in a pointer
// to a struct.
func Update(i interface{}) error {
	if !isPtr(i) {
		return NoPtr
	}

	err := addCurrentDateTime(i, "UpdatedAt")
	if err != nil {
		return err
	}

	s, err := GetSession()
	if err != nil {
		return err
	}
	defer s.Close()

	id, err := getObjIdFromStruct(i)
	if err != nil {
		return err
	}

	return GetColl(s, typeName(i)).Update(bson.M{"_id": id}, i)
}

// Deletes a record. Uses the Id to identify the record to delete. Must pass in a pointer
// to a struct.
func Delete(i interface{}) error {
	if !isPtr(i) {
		return NoPtr
	}

	s, err := GetSession()
	if err != nil {
		return err
	}
	defer s.Close()

	id, err := getObjIdFromStruct(i)
	if err != nil {
		return err
	}

	return GetColl(s, typeName(i)).RemoveId(id)
}

// Returns a Mongo session. You must call Session.Close() when you're done.
func GetSession() (*mgo.Session, error) {
	var err error

	if mgoSession == nil {
		mgoSession, err = mgo.Dial(servers)
		if err != nil {
			return nil, err
		}
	}

	return mgoSession.Clone(), nil
}

// We pass in the session because that is a clone of the original and the
// caller will need to close it when finished.
func GetColl(session *mgo.Session, coll string) *mgo.Collection {
	return session.DB(database).C(coll)
}

func getObjIdFromStruct(i interface{}) (bson.ObjectId, error) {
	v := reflect.ValueOf(i)

	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return bson.ObjectId(""), errors.New("Can't delete record. Type must be a struct.")
	}

	f := v.FieldByName("Id")
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	iface := f.Interface()
	if id, ok := iface.(bson.ObjectId); ok {
		return id, nil
	}

	if id, ok := iface.(bson.Getter); ok {
		objId, err := id.GetBSON()
		if err != nil {
			return bson.NewObjectId(), err
		}
		return objId.(bson.ObjectId), nil
	}

	return bson.NewObjectId(), fmt.Errorf("Unknown type in Id field. Expected string or bson.ObjectId. Received: %t", i)
}

func isPtr(i interface{}) bool {
	return reflect.ValueOf(i).Kind() == reflect.Ptr
}

func typeName(i interface{}) string {
	t := reflect.TypeOf(i)

	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if isSlice(t) {
		t = t.Elem()

		if t.Kind() == reflect.Ptr {
			t = t.Elem()
		}
	}

	return t.Name()
}

// returns true if the interface is a slice
func isSlice(t reflect.Type) bool {
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	return t.Kind() == reflect.Slice
}

func addNewFields(i interface{}) error {
	err := addId(i)
	if err != nil {
		return err
	}

	if err := addCurrentDateTime(i, "CreatedAt"); err != nil {
		return err
	}

	return addCurrentDateTime(i, "UpdatedAt")
}

func addCurrentDateTime(i interface{}, name string) error {
	if !hasStructField(i, name) {
		return nil
	}

	now := time.Now()

	v := reflect.ValueOf(i)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	f := v.FieldByName(name)
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	if reflect.TypeOf(now) != f.Type() {
		return fmt.Errorf("%v must be time.Time type.", name)
	}

	if !f.CanSet() {
		return fmt.Errorf("Couldn't set time for field: %v", name)
	}

	f.Set(reflect.ValueOf(now))

	return nil
}

func hasStructField(i interface{}, field string) bool {
	t := reflect.TypeOf(i)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if t.Kind() != reflect.Struct {
		return false
	}

	_, found := t.FieldByName(field)
	return found
}

func addId(i interface{}) error {
	v := reflect.ValueOf(i)

	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return errors.New("Record must be a struct")
	}

	f := v.FieldByName("Id")
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	if f.Kind() == reflect.String {
		id := f.Interface()
		if _, ok := id.(bson.ObjectId); ok {
			f.Set(reflect.ValueOf(bson.NewObjectId()))
		} else {
			f.SetString(bson.NewObjectId().Hex())
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.

package provider

import (
	"context"
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
	"github.com/oracle/oci-go-sdk/common"
	oci_core "github.com/oracle/oci-go-sdk/core"
)

const (
	VolumeRequiredOnlyResource = VolumeResourceDependencies + `
resource "oci_core_volume" "test_volume" {
	#Required
	availability_domain = "${data.oci_identity_availability_domains.ADs.availability_domains.0.name}"
	compartment_id = "${var.compartment_id}"
}
`

	VolumeResourceConfig = VolumeResourceDependencies + `
resource "oci_core_volume" "test_volume" {
	#Required
	availability_domain = "${data.oci_identity_availability_domains.ADs.availability_domains.0.name}"
	compartment_id = "${var.compartment_id}"

	#Optional
	defined_tags = "${map("${oci_identity_tag_namespace.tag-namespace1.name}.${oci_identity_tag.tag1.name}", "${var.volume_defined_tags_value}")}"
	display_name = "${var.volume_display_name}"
	freeform_tags = "${var.volume_freeform_tags}"
	size_in_gbs = "${var.volume_size_in_gbs}"
	source_details {
		#Required
		type = "${var.volume_source_details_type}"
		id = "${oci_core_volume.source_volume.id}"
	}
}
`
	VolumePropertyVariables = `
variable "volume_defined_tags_value" { default = "value" }
variable "volume_display_name" { default = "displayName" }
variable "volume_freeform_tags" { default = {"Department"= "Finance"} }
variable "volume_size_in_gbs" { default = 50 }
variable "volume_source_details_type" { default = "volume" }
variable "volume_state" { default = "AVAILABLE" }

`
	// Uncomment once defined: VolumeBackupPropertyVariables + VolumeBackupResourceConfig
	VolumeResourceDependencies = DefinedTagsDependencies + `
data "oci_identity_availability_domains" "ADs" {
	compartment_id = "${var.compartment_id}"
}

resource "oci_core_volume" "source_volume" {
	#Required
	availability_domain = "${data.oci_identity_availability_domains.ADs.availability_domains.0.name}"
	compartment_id = "${var.compartment_id}"
}
`
)

func TestCoreVolumeResource_basic(t *testing.T) {
	provider := testAccProvider
	config := testProviderConfig()

	compartmentId := getEnvSettingWithBlankDefault("compartment_ocid")
	compartmentIdVariableStr := fmt.Sprintf("variable \"compartment_id\" { default = \"%s\" }\n", compartmentId)

	resourceName := "oci_core_volume.test_volume"
	datasourceName := "data.oci_core_volumes.test_volumes"

	var resId, resId2 string

	resource.Test(t, resource.TestCase{
		PreCheck: func() { testAccPreCheck(t) },
		Providers: map[string]terraform.ResourceProvider{
			"oci": provider,
		},
		CheckDestroy: testAccCheckCoreVolumeDestroy,
		Steps: []resource.TestStep{
			// verify create
			{
				Config: config + VolumePropertyVariables + compartmentIdVariableStr + VolumeRequiredOnlyResource,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(resourceName, "availability_domain"),
					resource.TestCheckResourceAttr(resourceName, "compartment_id", compartmentId),

					func(s *terraform.State) (err error) {
						resId, err = fromInstanceState(s, resourceName, "id")
						return err
					},
				),
			},

			// delete before next create
			{
				Config: config + compartmentIdVariableStr + VolumeResourceDependencies,
			},
			// verify create with optionals
			{
				Config: config + VolumePropertyVariables + compartmentIdVariableStr + VolumeResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(resourceName, "availability_domain"),
					resource.TestCheckResourceAttr(resourceName, "compartment_id", compartmentId),
					resource.TestCheckResourceAttr(resourceName, "defined_tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "display_name", "displayName"),
					resource.TestCheckResourceAttr(resourceName, "freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttr(resourceName, "size_in_gbs", "50"),
					resource.TestCheckResourceAttr(resourceName, "size_in_mbs", "51200"),
					resource.TestCheckResourceAttr(resourceName, "source_details.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "source_details.0.type", "volume"),
					resource.TestCheckResourceAttrSet(resourceName, "state"),
					resource.TestCheckResourceAttrSet(resourceName, "time_created"),

					func(s *terraform.State) (err error) {
						resId, err = fromInstanceState(s, resourceName, "id")
						return err
					},
				),
			},

			// verify updates to updatable parameters
			{
				Config: config + `
variable "volume_defined_tags_value" { default = "updatedValue" }
variable "volume_display_name" { default = "displayName2" }
variable "volume_freeform_tags" { default = {"Department"= "Accounting"} }
variable "volume_size_in_gbs" { default = 50 }
variable "volume_source_details_type" { default = "volume" }
variable "volume_state" { default = "AVAILABLE" }

                ` + compartmentIdVariableStr + VolumeResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(resourceName, "availability_domain"),
					resource.TestCheckResourceAttr(resourceName, "compartment_id", compartmentId),
					resource.TestCheckResourceAttr(resourceName, "defined_tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "display_name", "displayName2"),
					resource.TestCheckResourceAttr(resourceName, "freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttr(resourceName, "size_in_gbs", "50"),
					resource.TestCheckResourceAttr(resourceName, "size_in_mbs", "51200"),
					resource.TestCheckResourceAttr(resourceName, "source_details.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "source_details.0.type", "volume"),
					resource.TestCheckResourceAttrSet(resourceName, "state"),
					resource.TestCheckResourceAttrSet(resourceName, "time_created"),

					func(s *terraform.State) (err error) {
						resId2, err = fromInstanceState(s, resourceName, "id")
						if resId != resId2 {
							return fmt.Errorf("Resource recreated when it was supposed to be updated.")
						}
						return err
					},
				),
			},
			// verify datasource
			{
				Config: config + `
variable "volume_defined_tags_value" { default = "updatedValue" }
variable "volume_display_name" { default = "displayName2" }
variable "volume_freeform_tags" { default = {"Department"= "Accounting"} }
variable "volume_size_in_gbs" { default = 50 }
variable "volume_source_details_type" { default = "volume" }
variable "volume_state" { default = "AVAILABLE" }

data "oci_core_volumes" "test_volumes" {
	#Required
	compartment_id = "${var.compartment_id}"

	#Optional
	availability_domain = "${data.oci_identity_availability_domains.ADs.availability_domains.0.name}"
	display_name = "${var.volume_display_name}"
	state = "${var.volume_state}"

    filter {
    	name = "id"
    	values = ["${oci_core_volume.test_volume.id}"]
    }
}
                ` + compartmentIdVariableStr + VolumeResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(datasourceName, "availability_domain"),
					resource.TestCheckResourceAttr(datasourceName, "compartment_id", compartmentId),
					resource.TestCheckResourceAttr(datasourceName, "display_name", "displayName2"),
					resource.TestCheckResourceAttrSet(datasourceName, "state"),

					resource.TestCheckResourceAttr(datasourceName, "volumes.#", "1"),
					resource.TestCheckResourceAttrSet(datasourceName, "volumes.0.availability_domain"),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.compartment_id", compartmentId),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.defined_tags.%", "1"),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.display_name", "displayName2"),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(datasourceName, "volumes.0.id"),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.size_in_gbs", "50"),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.size_in_mbs", "51200"),
					resource.TestCheckResourceAttrSet(datasourceName, "volumes.0.state"),
					resource.TestCheckResourceAttrSet(datasourceName, "volumes.0.time_created"),
				),
			},
			// verify resource import
			{
				Config:            config,
				ImportState:       true,
				ImportStateVerify: true,
				ImportStateVerifyIgnore: []string{
					"volume_backup_id",
				},
				ResourceName: resourceName,
			},
		},
	})
}

func testAccCheckCoreVolumeDestroy(s *terraform.State) error {
	noResourceFound := true
	client := testAccProvider.Meta().(*OracleClients).blockstorageClient
	for _, rs := range s.RootModule().Resources {
		if rs.Type == "oci_core_volume" {
			noResourceFound = false
			request := oci_core.GetVolumeRequest{}

			tmp := rs.Primary.ID
			request.VolumeId = &tmp

			response, err := client.GetVolume(context.Background(), request)

			if err == nil {
				deletedLifecycleStates := map[string]bool{
					string(oci_core.VolumeLifecycleStateTerminated): true,
				}
				if _, ok := deletedLifecycleStates[string(response.LifecycleState)]; !ok {
					//resource lifecycle state is not in expected deleted lifecycle states.
					return fmt.Errorf("resource lifecycle state: %s is not in expected deleted lifecycle states", response.LifecycleState)
				}
				//resource lifecycle state is in expected deleted lifecycle states. continue with next one.
				continue
			}

			//Verify that exception is for '404 not found'.
			if failure, isServiceError := common.IsServiceError(err); !isServiceError || failure.GetHTTPStatusCode() != 404 {
				return err
			}
		}
	}
	if noResourceFound {
		return fmt.Errorf("at least one resource was expected from the state file, but could not be found")
	}

	return nil
}
<commit_msg>Add tests for diff suppress and validation usages in int64 for volumes<commit_after>// Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.

package provider

import (
	"context"
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
	"github.com/oracle/oci-go-sdk/common"
	oci_core "github.com/oracle/oci-go-sdk/core"
	"regexp"
)

const (
	VolumeRequiredOnlyResource = VolumeResourceDependencies + `
resource "oci_core_volume" "test_volume" {
	#Required
	availability_domain = "${data.oci_identity_availability_domains.ADs.availability_domains.0.name}"
	compartment_id = "${var.compartment_id}"
}
`

	VolumeResourceConfig = VolumeResourceDependencies + `
resource "oci_core_volume" "test_volume" {
	#Required
	availability_domain = "${data.oci_identity_availability_domains.ADs.availability_domains.0.name}"
	compartment_id = "${var.compartment_id}"

	#Optional
	defined_tags = "${map("${oci_identity_tag_namespace.tag-namespace1.name}.${oci_identity_tag.tag1.name}", "${var.volume_defined_tags_value}")}"
	display_name = "${var.volume_display_name}"
	freeform_tags = "${var.volume_freeform_tags}"
	size_in_gbs = "${var.volume_size_in_gbs}"
	source_details {
		#Required
		type = "${var.volume_source_details_type}"
		id = "${oci_core_volume.source_volume.id}"
	}
}
`
	VolumePropertyVariables = `
variable "volume_defined_tags_value" { default = "value" }
variable "volume_display_name" { default = "displayName" }
variable "volume_freeform_tags" { default = {"Department"= "Finance"} }
variable "volume_size_in_gbs" { default = 50 }
variable "volume_source_details_type" { default = "volume" }
variable "volume_state" { default = "AVAILABLE" }

`
	// Uncomment once defined: VolumeBackupPropertyVariables + VolumeBackupResourceConfig
	VolumeResourceDependencies = DefinedTagsDependencies + `
data "oci_identity_availability_domains" "ADs" {
	compartment_id = "${var.compartment_id}"
}

resource "oci_core_volume" "source_volume" {
	#Required
	availability_domain = "${data.oci_identity_availability_domains.ADs.availability_domains.0.name}"
	compartment_id = "${var.compartment_id}"
}
`
)

func TestCoreVolumeResource_basic(t *testing.T) {
	provider := testAccProvider
	config := testProviderConfig()

	compartmentId := getEnvSettingWithBlankDefault("compartment_ocid")
	compartmentIdVariableStr := fmt.Sprintf("variable \"compartment_id\" { default = \"%s\" }\n", compartmentId)

	resourceName := "oci_core_volume.test_volume"
	datasourceName := "data.oci_core_volumes.test_volumes"

	var resId, resId2 string

	resource.Test(t, resource.TestCase{
		PreCheck: func() { testAccPreCheck(t) },
		Providers: map[string]terraform.ResourceProvider{
			"oci": provider,
		},
		CheckDestroy: testAccCheckCoreVolumeDestroy,
		Steps: []resource.TestStep{
			// verify create
			{
				Config: config + VolumePropertyVariables + compartmentIdVariableStr + VolumeRequiredOnlyResource,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(resourceName, "availability_domain"),
					resource.TestCheckResourceAttr(resourceName, "compartment_id", compartmentId),

					func(s *terraform.State) (err error) {
						resId, err = fromInstanceState(s, resourceName, "id")
						return err
					},
				),
			},

			// delete before next create
			{
				Config: config + compartmentIdVariableStr + VolumeResourceDependencies,
			},
			// verify create with optionals
			{
				Config: config + VolumePropertyVariables + compartmentIdVariableStr + VolumeResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(resourceName, "availability_domain"),
					resource.TestCheckResourceAttr(resourceName, "compartment_id", compartmentId),
					resource.TestCheckResourceAttr(resourceName, "defined_tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "display_name", "displayName"),
					resource.TestCheckResourceAttr(resourceName, "freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttr(resourceName, "size_in_gbs", "50"),
					resource.TestCheckResourceAttr(resourceName, "size_in_mbs", "51200"),
					resource.TestCheckResourceAttr(resourceName, "source_details.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "source_details.0.type", "volume"),
					resource.TestCheckResourceAttrSet(resourceName, "state"),
					resource.TestCheckResourceAttrSet(resourceName, "time_created"),

					func(s *terraform.State) (err error) {
						resId, err = fromInstanceState(s, resourceName, "id")
						return err
					},
				),
			},

			// verify updates to updatable parameters
			{
				Config: config + `
variable "volume_defined_tags_value" { default = "updatedValue" }
variable "volume_display_name" { default = "displayName2" }
variable "volume_freeform_tags" { default = {"Department"= "Accounting"} }
variable "volume_size_in_gbs" { default = 50 }
variable "volume_source_details_type" { default = "volume" }
variable "volume_state" { default = "AVAILABLE" }

                ` + compartmentIdVariableStr + VolumeResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(resourceName, "availability_domain"),
					resource.TestCheckResourceAttr(resourceName, "compartment_id", compartmentId),
					resource.TestCheckResourceAttr(resourceName, "defined_tags.%", "1"),
					resource.TestCheckResourceAttr(resourceName, "display_name", "displayName2"),
					resource.TestCheckResourceAttr(resourceName, "freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(resourceName, "id"),
					resource.TestCheckResourceAttr(resourceName, "size_in_gbs", "50"),
					resource.TestCheckResourceAttr(resourceName, "size_in_mbs", "51200"),
					resource.TestCheckResourceAttr(resourceName, "source_details.#", "1"),
					resource.TestCheckResourceAttr(resourceName, "source_details.0.type", "volume"),
					resource.TestCheckResourceAttrSet(resourceName, "state"),
					resource.TestCheckResourceAttrSet(resourceName, "time_created"),

					func(s *terraform.State) (err error) {
						resId2, err = fromInstanceState(s, resourceName, "id")
						if resId != resId2 {
							return fmt.Errorf("Resource recreated when it was supposed to be updated.")
						}
						return err
					},
				),
			},
			// ensure that changing datatype of size_in_gbs is a no-op
			{
				Config: config + `
variable "volume_defined_tags_value" { default = "updatedValue" }
variable "volume_display_name" { default = "displayName2" }
variable "volume_freeform_tags" { default = {"Department"= "Accounting"} }
variable "volume_size_in_gbs" { default = "50" }
variable "volume_source_details_type" { default = "volume" }
variable "volume_state" { default = "AVAILABLE" }

                ` + compartmentIdVariableStr + VolumeResourceConfig,
				PlanOnly:           true,
				ExpectNonEmptyPlan: false,
			},
			// ensure that adding leading zeroes to size_in_gbs is a no-op
			{
				Config: config + `
variable "volume_defined_tags_value" { default = "updatedValue" }
variable "volume_display_name" { default = "displayName2" }
variable "volume_freeform_tags" { default = {"Department"= "Accounting"} }
variable "volume_size_in_gbs" { default = "0050" }
variable "volume_source_details_type" { default = "volume" }
variable "volume_state" { default = "AVAILABLE" }

                ` + compartmentIdVariableStr + VolumeResourceConfig,
				PlanOnly:           true,
				ExpectNonEmptyPlan: false,
			},
			// ensure that giving non-numeric characters in size_in_gbs will yield an error
			{
				Config: config + `
variable "volume_defined_tags_value" { default = "updatedValue" }
variable "volume_display_name" { default = "displayName2" }
variable "volume_freeform_tags" { default = {"Department"= "Accounting"} }
variable "volume_size_in_gbs" { default = "abc" }
variable "volume_source_details_type" { default = "volume" }
variable "volume_state" { default = "AVAILABLE" }

                ` + compartmentIdVariableStr + VolumeResourceConfig,
				PlanOnly:    true,
				ExpectError: regexp.MustCompile("must be a 64-bit integer"),
			},
			// verify datasource
			{
				Config: config + `
variable "volume_defined_tags_value" { default = "updatedValue" }
variable "volume_display_name" { default = "displayName2" }
variable "volume_freeform_tags" { default = {"Department"= "Accounting"} }
variable "volume_size_in_gbs" { default = 50 }
variable "volume_source_details_type" { default = "volume" }
variable "volume_state" { default = "AVAILABLE" }

data "oci_core_volumes" "test_volumes" {
	#Required
	compartment_id = "${var.compartment_id}"

	#Optional
	availability_domain = "${data.oci_identity_availability_domains.ADs.availability_domains.0.name}"
	display_name = "${var.volume_display_name}"
	state = "${var.volume_state}"

    filter {
    	name = "id"
    	values = ["${oci_core_volume.test_volume.id}"]
    }
}
                ` + compartmentIdVariableStr + VolumeResourceConfig,
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(datasourceName, "availability_domain"),
					resource.TestCheckResourceAttr(datasourceName, "compartment_id", compartmentId),
					resource.TestCheckResourceAttr(datasourceName, "display_name", "displayName2"),
					resource.TestCheckResourceAttrSet(datasourceName, "state"),

					resource.TestCheckResourceAttr(datasourceName, "volumes.#", "1"),
					resource.TestCheckResourceAttrSet(datasourceName, "volumes.0.availability_domain"),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.compartment_id", compartmentId),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.defined_tags.%", "1"),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.display_name", "displayName2"),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.freeform_tags.%", "1"),
					resource.TestCheckResourceAttrSet(datasourceName, "volumes.0.id"),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.size_in_gbs", "50"),
					resource.TestCheckResourceAttr(datasourceName, "volumes.0.size_in_mbs", "51200"),
					resource.TestCheckResourceAttrSet(datasourceName, "volumes.0.state"),
					resource.TestCheckResourceAttrSet(datasourceName, "volumes.0.time_created"),
				),
			},
			// verify resource import
			{
				Config:            config,
				ImportState:       true,
				ImportStateVerify: true,
				ImportStateVerifyIgnore: []string{
					"volume_backup_id",
				},
				ResourceName: resourceName,
			},
		},
	})
}

func testAccCheckCoreVolumeDestroy(s *terraform.State) error {
	noResourceFound := true
	client := testAccProvider.Meta().(*OracleClients).blockstorageClient
	for _, rs := range s.RootModule().Resources {
		if rs.Type == "oci_core_volume" {
			noResourceFound = false
			request := oci_core.GetVolumeRequest{}

			tmp := rs.Primary.ID
			request.VolumeId = &tmp

			response, err := client.GetVolume(context.Background(), request)

			if err == nil {
				deletedLifecycleStates := map[string]bool{
					string(oci_core.VolumeLifecycleStateTerminated): true,
				}
				if _, ok := deletedLifecycleStates[string(response.LifecycleState)]; !ok {
					//resource lifecycle state is not in expected deleted lifecycle states.
					return fmt.Errorf("resource lifecycle state: %s is not in expected deleted lifecycle states", response.LifecycleState)
				}
				//resource lifecycle state is in expected deleted lifecycle states. continue with next one.
				continue
			}

			//Verify that exception is for '404 not found'.
			if failure, isServiceError := common.IsServiceError(err); !isServiceError || failure.GetHTTPStatusCode() != 404 {
				return err
			}
		}
	}
	if noResourceFound {
		return fmt.Errorf("at least one resource was expected from the state file, but could not be found")
	}

	return nil
}
<|endoftext|>


<commit_before>package cloudflare

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/StackExchange/dnscontrol/v3/models"
)

// get list of domains for account. Cache so the ids can be looked up from domain name
func (c *cloudflareProvider) fetchDomainList() error {
	c.domainIndex = map[string]string{}
	c.nameservers = map[string][]string{}
	zones, err := c.cfClient.ListZones(context.Background())
	if err != nil {
		return fmt.Errorf("failed fetching domain list from cloudflare(%q): %s", c.cfClient.APIEmail, err)
	}

	for _, zone := range zones {
		c.domainIndex[zone.Name] = zone.ID
		c.nameservers[zone.Name] = append(c.nameservers[zone.Name], zone.NameServers...)
	}

	return nil
}

// get all records for a domain
func (c *cloudflareProvider) getRecordsForDomain(id string, domain string) ([]*models.RecordConfig, error) {
	records := []*models.RecordConfig{}
	rrs, err := c.cfClient.DNSRecords(context.Background(), id, cloudflare.DNSRecord{})
	if err != nil {
		return nil, fmt.Errorf("failed fetching record list from cloudflare(%q): %w", c.cfClient.APIEmail, err)
	}
	for _, rec := range rrs {
		rt, err := c.nativeToRecord(domain, rec)
		if err != nil {
			return nil, err
		}
		records = append(records, rt)
	}
	return records, nil
}

// create a correction to delete a record
func (c *cloudflareProvider) deleteRec(rec cloudflare.DNSRecord, domainID string) *models.Correction {
	return &models.Correction{
		Msg: fmt.Sprintf("DELETE record: %s %s %d %q (id=%s)", rec.Name, rec.Type, rec.TTL, rec.Content, rec.ID),
		F: func() error {
			err := c.cfClient.DeleteDNSRecord(context.Background(), domainID, rec.ID)
			return err
		},
	}
}

func (c *cloudflareProvider) createZone(domainName string) (string, error) {
	zone, err := c.cfClient.CreateZone(context.Background(), domainName, false, cloudflare.Account{ID: c.cfClient.AccountID}, "full")
	return zone.ID, err
}

func cfDSData(rec *models.RecordConfig) *cfRecData {
	return &cfRecData{
		KeyTag:     rec.DsKeyTag,
		Algorithm:  rec.DsAlgorithm,
		DigestType: rec.DsDigestType,
		Digest:     rec.DsDigest,
	}
}

func cfSrvData(rec *models.RecordConfig) *cfRecData {
	serverParts := strings.Split(rec.GetLabelFQDN(), ".")
	c := &cfRecData{
		Service:  serverParts[0],
		Proto:    serverParts[1],
		Name:     strings.Join(serverParts[2:], "."),
		Port:     rec.SrvPort,
		Priority: rec.SrvPriority,
		Weight:   rec.SrvWeight,
	}
	c.Target = cfTarget(rec.GetTargetField())
	return c
}

func cfCaaData(rec *models.RecordConfig) *cfRecData {
	return &cfRecData{
		Tag:   rec.CaaTag,
		Flags: rec.CaaFlag,
		Value: rec.GetTargetField(),
	}
}

func cfTlsaData(rec *models.RecordConfig) *cfRecData {
	return &cfRecData{
		Usage:        rec.TlsaUsage,
		Selector:     rec.TlsaSelector,
		MatchingType: rec.TlsaMatchingType,
		Certificate:  rec.GetTargetField(),
	}
}

func cfSshfpData(rec *models.RecordConfig) *cfRecData {
	return &cfRecData{
		Algorithm:   rec.SshfpAlgorithm,
		HashType:    rec.SshfpFingerprint,
		Fingerprint: rec.GetTargetField(),
	}
}

func (c *cloudflareProvider) createRec(rec *models.RecordConfig, domainID string) []*models.Correction {
	var id string
	content := rec.GetTargetField()
	if rec.Metadata[metaOriginalIP] != "" {
		content = rec.Metadata[metaOriginalIP]
	}
	prio := ""
	if rec.Type == "MX" {
		prio = fmt.Sprintf(" %d ", rec.MxPreference)
	}
	if rec.Type == "TXT" {
		content = rec.GetTargetTXTJoined()
	}
	if rec.Type == "DS" {
		content = fmt.Sprintf("%d %d %d %s", rec.DsKeyTag, rec.DsAlgorithm, rec.DsDigestType, rec.DsDigest)
	}
	arr := []*models.Correction{{
		Msg: fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.GetLabel(), rec.Type, rec.TTL, prio, content),
		F: func() error {
			cf := cloudflare.DNSRecord{
				Name:     rec.GetLabel(),
				Type:     rec.Type,
				TTL:      int(rec.TTL),
				Content:  content,
				Priority: &rec.MxPreference,
			}
			if rec.Type == "SRV" {
				cf.Data = cfSrvData(rec)
				cf.Name = rec.GetLabelFQDN()
			} else if rec.Type == "CAA" {
				cf.Data = cfCaaData(rec)
				cf.Name = rec.GetLabelFQDN()
				cf.Content = ""
			} else if rec.Type == "TLSA" {
				cf.Data = cfTlsaData(rec)
				cf.Name = rec.GetLabelFQDN()
			} else if rec.Type == "SSHFP" {
				cf.Data = cfSshfpData(rec)
				cf.Name = rec.GetLabelFQDN()
			} else if rec.Type == "DS" {
				cf.Data = cfDSData(rec)
			}
			resp, err := c.cfClient.CreateDNSRecord(context.Background(), domainID, cf)
			if err != nil {
				return err
			}
			// Updating id (from the outer scope) by side-effect, required for updating proxy mode
			id = resp.Result.ID
			return nil
		},
	}}
	if rec.Metadata[metaProxy] != "off" {
		arr = append(arr, &models.Correction{
			Msg: fmt.Sprintf("ACTIVATE PROXY for new record %s %s %d %s", rec.GetLabel(), rec.Type, rec.TTL, rec.GetTargetField()),
			F:   func() error { return c.modifyRecord(domainID, id, true, rec) },
		})
	}
	return arr
}

func (c *cloudflareProvider) modifyRecord(domainID, recID string, proxied bool, rec *models.RecordConfig) error {
	if domainID == "" || recID == "" {
		return fmt.Errorf("cannot modify record if domain or record id are empty")
	}

	r := cloudflare.DNSRecord{
		ID:       recID,
		Proxied:  &proxied,
		Name:     rec.GetLabel(),
		Type:     rec.Type,
		Content:  rec.GetTargetField(),
		Priority: &rec.MxPreference,
		TTL:      int(rec.TTL),
	}
	if rec.Type == "TXT" {
		r.Content = rec.GetTargetTXTJoined()
	}
	if rec.Type == "SRV" {
		r.Data = cfSrvData(rec)
		r.Name = rec.GetLabelFQDN()
	} else if rec.Type == "CAA" {
		r.Data = cfCaaData(rec)
		r.Name = rec.GetLabelFQDN()
		r.Content = ""
	} else if rec.Type == "TLSA" {
		r.Data = cfTlsaData(rec)
		r.Name = rec.GetLabelFQDN()
	} else if rec.Type == "SSHFP" {
		r.Data = cfSshfpData(rec)
		r.Name = rec.GetLabelFQDN()
	} else if rec.Type == "DS" {
		r.Data = cfDSData(rec)
		r.Content = ""
	}
	return c.cfClient.UpdateDNSRecord(context.Background(), domainID, recID, r)
}

// change universal ssl state
func (c *cloudflareProvider) changeUniversalSSL(domainID string, state bool) error {
	_, err := c.cfClient.EditUniversalSSLSetting(context.Background(), domainID, cloudflare.UniversalSSLSetting{Enabled: state})
	return err
}

// get universal ssl state
func (c *cloudflareProvider) getUniversalSSL(domainID string) (bool, error) {
	result, err := c.cfClient.UniversalSSLSettingDetails(context.Background(), domainID)
	return result.Enabled, err
}

func (c *cloudflareProvider) getPageRules(id string, domain string) ([]*models.RecordConfig, error) {
	rules, err := c.cfClient.ListPageRules(context.Background(), id)
	if err != nil {
		return nil, fmt.Errorf("failed fetching page rule list cloudflare: %s", err)
	}
	recs := []*models.RecordConfig{}
	for _, pr := range rules {
		// only interested in forwarding rules. Lets be very specific, and skip anything else
		if len(pr.Actions) != 1 || len(pr.Targets) != 1 {
			continue
		}
		if pr.Actions[0].ID != "forwarding_url" {
			continue
		}
		value := pr.Actions[0].Value.(map[string]interface{})
		var thisPr = pr
		r := &models.RecordConfig{
			Type:     "PAGE_RULE",
			Original: thisPr,
			TTL:      1,
		}
		r.SetLabel("@", domain)
		r.SetTarget(fmt.Sprintf("%s,%s,%d,%d", // $FROM,$TO,$PRIO,$CODE
			pr.Targets[0].Constraint.Value,
			value["url"],
			pr.Priority,
			intZero(value["status_code"])))
		recs = append(recs, r)
	}
	return recs, nil
}

func (c *cloudflareProvider) deletePageRule(recordID, domainID string) error {
	return c.cfClient.DeletePageRule(context.Background(), domainID, recordID)
}

func (c *cloudflareProvider) updatePageRule(recordID, domainID string, target string) error {
	// maybe someday?
	//c.apiProvider.UpdatePageRule(context.Background(), domainId, recordID, )
	if err := c.deletePageRule(recordID, domainID); err != nil {
		return err
	}
	return c.createPageRule(domainID, target)
}

func (c *cloudflareProvider) createPageRule(domainID string, target string) error {
	// from to priority code
	parts := strings.Split(target, ",")
	priority, _ := strconv.Atoi(parts[2])
	code, _ := strconv.Atoi(parts[3])
	pr := cloudflare.PageRule{
		Status:   "active",
		Priority: priority,
		Targets: []cloudflare.PageRuleTarget{
			{Target: "url", Constraint: pageRuleConstraint{Operator: "matches", Value: parts[0]}},
		},
		Actions: []cloudflare.PageRuleAction{
			{ID: "forwarding_url", Value: &pageRuleFwdInfo{
				StatusCode: code,
				URL:        parts[1],
			}},
		},
	}
	_, err := c.cfClient.CreatePageRule(context.Background(), domainID, pr)
	return err
}

func (c *cloudflareProvider) getWorkerRoutes(id string, domain string) ([]*models.RecordConfig, error) {
	res, err := c.cfClient.ListWorkerRoutes(context.Background(), id)
	if err != nil {
		return nil, fmt.Errorf("failed fetching worker route list cloudflare: %s", err)
	}

	recs := []*models.RecordConfig{}
	for _, pr := range res.Routes {
		var thisPr = pr
		r := &models.RecordConfig{
			Type:     "WORKER_ROUTE",
			Original: thisPr,
			TTL:      1,
		}
		r.SetLabel("@", domain)
		r.SetTarget(fmt.Sprintf("%s,%s", // $PATTERN,$SCRIPT
			pr.Pattern,
			pr.Script))
		recs = append(recs, r)
	}
	return recs, nil
}

func (c *cloudflareProvider) deleteWorkerRoute(recordID, domainID string) error {
	_, err := c.cfClient.DeleteWorkerRoute(context.Background(), domainID, recordID)
	return err
}

func (c *cloudflareProvider) updateWorkerRoute(recordID, domainID string, target string) error {
	// Causing Stack Overflow (!?)
	// return c.updateWorkerRoute(recordID, domainID, target)

	if err := c.deleteWorkerRoute(recordID, domainID); err != nil {
		return err
	}
	return c.createWorkerRoute(domainID, target)
}

func (c *cloudflareProvider) createWorkerRoute(domainID string, target string) error {
	// $PATTERN,$SCRIPT
	parts := strings.Split(target, ",")
	if len(parts) != 2 {
		return fmt.Errorf("unexpected target: '%s' (expected: 'PATTERN,SCRIPT')", target)
	}
	wr := cloudflare.WorkerRoute{
		Pattern: parts[0],
		Script:  parts[1],
	}

	_, err := c.cfClient.CreateWorkerRoute(context.Background(), domainID, wr)
	return err
}

func (c *cloudflareProvider) createTestWorker(workerName string) error {
	wrp := cloudflare.WorkerRequestParams{
		ZoneID:     "",
		ScriptName: workerName,
	}

	script := cloudflare.WorkerScriptParams{
		Script: `
			addEventListener("fetch", (event) => {
				event.respondWith(
					new Response("Ok.", { status: 200 })
				);
			});`,
	}

	_, err := c.cfClient.UploadWorker(context.Background(), &wrp, &script)
	return err
}

// https://github.com/dominikh/go-tools/issues/1137 which is a dup of
// https://github.com/dominikh/go-tools/issues/810
//
//lint:ignore U1000 false positive due to
type pageRuleConstraint struct {
	Operator string `json:"operator"`
	Value    string `json:"value"`
}

type pageRuleFwdInfo struct {
	URL        string `json:"url"`
	StatusCode int    `json:"status_code"`
}
<commit_msg>BUG: Re-introducing the `cloudflare/cloudflare-go` import (#1795)<commit_after>package cloudflare

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/StackExchange/dnscontrol/v3/models"
	"github.com/cloudflare/cloudflare-go"
)

// get list of domains for account. Cache so the ids can be looked up from domain name
func (c *cloudflareProvider) fetchDomainList() error {
	c.domainIndex = map[string]string{}
	c.nameservers = map[string][]string{}
	zones, err := c.cfClient.ListZones(context.Background())
	if err != nil {
		return fmt.Errorf("failed fetching domain list from cloudflare(%q): %s", c.cfClient.APIEmail, err)
	}

	for _, zone := range zones {
		c.domainIndex[zone.Name] = zone.ID
		c.nameservers[zone.Name] = append(c.nameservers[zone.Name], zone.NameServers...)
	}

	return nil
}

// get all records for a domain
func (c *cloudflareProvider) getRecordsForDomain(id string, domain string) ([]*models.RecordConfig, error) {
	records := []*models.RecordConfig{}
	rrs, err := c.cfClient.DNSRecords(context.Background(), id, cloudflare.DNSRecord{})
	if err != nil {
		return nil, fmt.Errorf("failed fetching record list from cloudflare(%q): %w", c.cfClient.APIEmail, err)
	}
	for _, rec := range rrs {
		rt, err := c.nativeToRecord(domain, rec)
		if err != nil {
			return nil, err
		}
		records = append(records, rt)
	}
	return records, nil
}

// create a correction to delete a record
func (c *cloudflareProvider) deleteRec(rec cloudflare.DNSRecord, domainID string) *models.Correction {
	return &models.Correction{
		Msg: fmt.Sprintf("DELETE record: %s %s %d %q (id=%s)", rec.Name, rec.Type, rec.TTL, rec.Content, rec.ID),
		F: func() error {
			err := c.cfClient.DeleteDNSRecord(context.Background(), domainID, rec.ID)
			return err
		},
	}
}

func (c *cloudflareProvider) createZone(domainName string) (string, error) {
	zone, err := c.cfClient.CreateZone(context.Background(), domainName, false, cloudflare.Account{ID: c.cfClient.AccountID}, "full")
	return zone.ID, err
}

func cfDSData(rec *models.RecordConfig) *cfRecData {
	return &cfRecData{
		KeyTag:     rec.DsKeyTag,
		Algorithm:  rec.DsAlgorithm,
		DigestType: rec.DsDigestType,
		Digest:     rec.DsDigest,
	}
}

func cfSrvData(rec *models.RecordConfig) *cfRecData {
	serverParts := strings.Split(rec.GetLabelFQDN(), ".")
	c := &cfRecData{
		Service:  serverParts[0],
		Proto:    serverParts[1],
		Name:     strings.Join(serverParts[2:], "."),
		Port:     rec.SrvPort,
		Priority: rec.SrvPriority,
		Weight:   rec.SrvWeight,
	}
	c.Target = cfTarget(rec.GetTargetField())
	return c
}

func cfCaaData(rec *models.RecordConfig) *cfRecData {
	return &cfRecData{
		Tag:   rec.CaaTag,
		Flags: rec.CaaFlag,
		Value: rec.GetTargetField(),
	}
}

func cfTlsaData(rec *models.RecordConfig) *cfRecData {
	return &cfRecData{
		Usage:        rec.TlsaUsage,
		Selector:     rec.TlsaSelector,
		MatchingType: rec.TlsaMatchingType,
		Certificate:  rec.GetTargetField(),
	}
}

func cfSshfpData(rec *models.RecordConfig) *cfRecData {
	return &cfRecData{
		Algorithm:   rec.SshfpAlgorithm,
		HashType:    rec.SshfpFingerprint,
		Fingerprint: rec.GetTargetField(),
	}
}

func (c *cloudflareProvider) createRec(rec *models.RecordConfig, domainID string) []*models.Correction {
	var id string
	content := rec.GetTargetField()
	if rec.Metadata[metaOriginalIP] != "" {
		content = rec.Metadata[metaOriginalIP]
	}
	prio := ""
	if rec.Type == "MX" {
		prio = fmt.Sprintf(" %d ", rec.MxPreference)
	}
	if rec.Type == "TXT" {
		content = rec.GetTargetTXTJoined()
	}
	if rec.Type == "DS" {
		content = fmt.Sprintf("%d %d %d %s", rec.DsKeyTag, rec.DsAlgorithm, rec.DsDigestType, rec.DsDigest)
	}
	arr := []*models.Correction{{
		Msg: fmt.Sprintf("CREATE record: %s %s %d%s %s", rec.GetLabel(), rec.Type, rec.TTL, prio, content),
		F: func() error {
			cf := cloudflare.DNSRecord{
				Name:     rec.GetLabel(),
				Type:     rec.Type,
				TTL:      int(rec.TTL),
				Content:  content,
				Priority: &rec.MxPreference,
			}
			if rec.Type == "SRV" {
				cf.Data = cfSrvData(rec)
				cf.Name = rec.GetLabelFQDN()
			} else if rec.Type == "CAA" {
				cf.Data = cfCaaData(rec)
				cf.Name = rec.GetLabelFQDN()
				cf.Content = ""
			} else if rec.Type == "TLSA" {
				cf.Data = cfTlsaData(rec)
				cf.Name = rec.GetLabelFQDN()
			} else if rec.Type == "SSHFP" {
				cf.Data = cfSshfpData(rec)
				cf.Name = rec.GetLabelFQDN()
			} else if rec.Type == "DS" {
				cf.Data = cfDSData(rec)
			}
			resp, err := c.cfClient.CreateDNSRecord(context.Background(), domainID, cf)
			if err != nil {
				return err
			}
			// Updating id (from the outer scope) by side-effect, required for updating proxy mode
			id = resp.Result.ID
			return nil
		},
	}}
	if rec.Metadata[metaProxy] != "off" {
		arr = append(arr, &models.Correction{
			Msg: fmt.Sprintf("ACTIVATE PROXY for new record %s %s %d %s", rec.GetLabel(), rec.Type, rec.TTL, rec.GetTargetField()),
			F:   func() error { return c.modifyRecord(domainID, id, true, rec) },
		})
	}
	return arr
}

func (c *cloudflareProvider) modifyRecord(domainID, recID string, proxied bool, rec *models.RecordConfig) error {
	if domainID == "" || recID == "" {
		return fmt.Errorf("cannot modify record if domain or record id are empty")
	}

	r := cloudflare.DNSRecord{
		ID:       recID,
		Proxied:  &proxied,
		Name:     rec.GetLabel(),
		Type:     rec.Type,
		Content:  rec.GetTargetField(),
		Priority: &rec.MxPreference,
		TTL:      int(rec.TTL),
	}
	if rec.Type == "TXT" {
		r.Content = rec.GetTargetTXTJoined()
	}
	if rec.Type == "SRV" {
		r.Data = cfSrvData(rec)
		r.Name = rec.GetLabelFQDN()
	} else if rec.Type == "CAA" {
		r.Data = cfCaaData(rec)
		r.Name = rec.GetLabelFQDN()
		r.Content = ""
	} else if rec.Type == "TLSA" {
		r.Data = cfTlsaData(rec)
		r.Name = rec.GetLabelFQDN()
	} else if rec.Type == "SSHFP" {
		r.Data = cfSshfpData(rec)
		r.Name = rec.GetLabelFQDN()
	} else if rec.Type == "DS" {
		r.Data = cfDSData(rec)
		r.Content = ""
	}
	return c.cfClient.UpdateDNSRecord(context.Background(), domainID, recID, r)
}

// change universal ssl state
func (c *cloudflareProvider) changeUniversalSSL(domainID string, state bool) error {
	_, err := c.cfClient.EditUniversalSSLSetting(context.Background(), domainID, cloudflare.UniversalSSLSetting{Enabled: state})
	return err
}

// get universal ssl state
func (c *cloudflareProvider) getUniversalSSL(domainID string) (bool, error) {
	result, err := c.cfClient.UniversalSSLSettingDetails(context.Background(), domainID)
	return result.Enabled, err
}

func (c *cloudflareProvider) getPageRules(id string, domain string) ([]*models.RecordConfig, error) {
	rules, err := c.cfClient.ListPageRules(context.Background(), id)
	if err != nil {
		return nil, fmt.Errorf("failed fetching page rule list cloudflare: %s", err)
	}
	recs := []*models.RecordConfig{}
	for _, pr := range rules {
		// only interested in forwarding rules. Lets be very specific, and skip anything else
		if len(pr.Actions) != 1 || len(pr.Targets) != 1 {
			continue
		}
		if pr.Actions[0].ID != "forwarding_url" {
			continue
		}
		value := pr.Actions[0].Value.(map[string]interface{})
		var thisPr = pr
		r := &models.RecordConfig{
			Type:     "PAGE_RULE",
			Original: thisPr,
			TTL:      1,
		}
		r.SetLabel("@", domain)
		r.SetTarget(fmt.Sprintf("%s,%s,%d,%d", // $FROM,$TO,$PRIO,$CODE
			pr.Targets[0].Constraint.Value,
			value["url"],
			pr.Priority,
			intZero(value["status_code"])))
		recs = append(recs, r)
	}
	return recs, nil
}

func (c *cloudflareProvider) deletePageRule(recordID, domainID string) error {
	return c.cfClient.DeletePageRule(context.Background(), domainID, recordID)
}

func (c *cloudflareProvider) updatePageRule(recordID, domainID string, target string) error {
	// maybe someday?
	//c.apiProvider.UpdatePageRule(context.Background(), domainId, recordID, )
	if err := c.deletePageRule(recordID, domainID); err != nil {
		return err
	}
	return c.createPageRule(domainID, target)
}

func (c *cloudflareProvider) createPageRule(domainID string, target string) error {
	// from to priority code
	parts := strings.Split(target, ",")
	priority, _ := strconv.Atoi(parts[2])
	code, _ := strconv.Atoi(parts[3])
	pr := cloudflare.PageRule{
		Status:   "active",
		Priority: priority,
		Targets: []cloudflare.PageRuleTarget{
			{Target: "url", Constraint: pageRuleConstraint{Operator: "matches", Value: parts[0]}},
		},
		Actions: []cloudflare.PageRuleAction{
			{ID: "forwarding_url", Value: &pageRuleFwdInfo{
				StatusCode: code,
				URL:        parts[1],
			}},
		},
	}
	_, err := c.cfClient.CreatePageRule(context.Background(), domainID, pr)
	return err
}

func (c *cloudflareProvider) getWorkerRoutes(id string, domain string) ([]*models.RecordConfig, error) {
	res, err := c.cfClient.ListWorkerRoutes(context.Background(), id)
	if err != nil {
		return nil, fmt.Errorf("failed fetching worker route list cloudflare: %s", err)
	}

	recs := []*models.RecordConfig{}
	for _, pr := range res.Routes {
		var thisPr = pr
		r := &models.RecordConfig{
			Type:     "WORKER_ROUTE",
			Original: thisPr,
			TTL:      1,
		}
		r.SetLabel("@", domain)
		r.SetTarget(fmt.Sprintf("%s,%s", // $PATTERN,$SCRIPT
			pr.Pattern,
			pr.Script))
		recs = append(recs, r)
	}
	return recs, nil
}

func (c *cloudflareProvider) deleteWorkerRoute(recordID, domainID string) error {
	_, err := c.cfClient.DeleteWorkerRoute(context.Background(), domainID, recordID)
	return err
}

func (c *cloudflareProvider) updateWorkerRoute(recordID, domainID string, target string) error {
	// Causing Stack Overflow (!?)
	// return c.updateWorkerRoute(recordID, domainID, target)

	if err := c.deleteWorkerRoute(recordID, domainID); err != nil {
		return err
	}
	return c.createWorkerRoute(domainID, target)
}

func (c *cloudflareProvider) createWorkerRoute(domainID string, target string) error {
	// $PATTERN,$SCRIPT
	parts := strings.Split(target, ",")
	if len(parts) != 2 {
		return fmt.Errorf("unexpected target: '%s' (expected: 'PATTERN,SCRIPT')", target)
	}
	wr := cloudflare.WorkerRoute{
		Pattern: parts[0],
		Script:  parts[1],
	}

	_, err := c.cfClient.CreateWorkerRoute(context.Background(), domainID, wr)
	return err
}

func (c *cloudflareProvider) createTestWorker(workerName string) error {
	wrp := cloudflare.WorkerRequestParams{
		ZoneID:     "",
		ScriptName: workerName,
	}

	script := cloudflare.WorkerScriptParams{
		Script: `
			addEventListener("fetch", (event) => {
				event.respondWith(
					new Response("Ok.", { status: 200 })
				);
			});`,
	}

	_, err := c.cfClient.UploadWorker(context.Background(), &wrp, &script)
	return err
}

// https://github.com/dominikh/go-tools/issues/1137 which is a dup of
// https://github.com/dominikh/go-tools/issues/810
//
//lint:ignore U1000 false positive due to
type pageRuleConstraint struct {
	Operator string `json:"operator"`
	Value    string `json:"value"`
}

type pageRuleFwdInfo struct {
	URL        string `json:"url"`
	StatusCode int    `json:"status_code"`
}
<|endoftext|>


<commit_before>// Copyright 2018 The Terraformer 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 datadog

import (
	"context"
	"fmt"
	"strconv"

	datadogV1 "github.com/DataDog/datadog-api-client-go/api/v1/datadog"

	"github.com/GoogleCloudPlatform/terraformer/terraformutils"
)

var (
	// MonitorAllowEmptyValues ...
	MonitorAllowEmptyValues = []string{"tags."}
)

// MonitorGenerator ...
type MonitorGenerator struct {
	DatadogService
}

func (g *MonitorGenerator) createResources(monitors []datadogV1.Monitor) []terraformutils.Resource {
	resources := []terraformutils.Resource{}
	for _, monitor := range monitors {
		if monitor.GetType() == datadogV1.MONITORTYPE_SYNTHETICS_ALERT {
			continue
		}
		resourceName := strconv.FormatInt(monitor.GetId(), 10)
		resources = append(resources, g.createResource(resourceName))
	}

	return resources
}

func (g *MonitorGenerator) createResource(monitorID string) terraformutils.Resource {
	return terraformutils.NewSimpleResource(
		monitorID,
		fmt.Sprintf("monitor_%s", monitorID),
		"datadog_monitor",
		"datadog",
		MonitorAllowEmptyValues,
	)
}

// InitResources Generate TerraformResources from Datadog API,
// from each monitor create 1 TerraformResource.
// Need Monitor ID as ID for terraform resource
func (g *MonitorGenerator) InitResources() error {
	datadogClientV1 := g.Args["datadogClientV1"].(*datadogV1.APIClient)
	authV1 := g.Args["authV1"].(context.Context)

	resources := []terraformutils.Resource{}
	for _, filter := range g.Filter {
		if filter.FieldPath == "id" && filter.IsApplicable("monitor") {
			for _, value := range filter.AcceptableValues {
				i, err := strconv.ParseInt(value, 10, 64)
				if err != nil {
					return err
				}

				monitor, _, err := datadogClientV1.MonitorsApi.GetMonitor(authV1, i).Execute()
				if err != nil {
					return err
				}

				resources = append(resources, g.createResource(strconv.FormatInt(monitor.GetId(), 10)))
			}
		}
	}

	if len(resources) > 0 {
		g.Resources = resources
		return nil
	}

	var monitors []datadogV1.Monitor
	pageSize := int32(1000)
	pageNumber := int64(0)
	for {
		resp, _, err := datadogClientV1.MonitorsApi.ListMonitors(authV1).PageSize(pageSize).Page(pageNumber).Execute()
		if err != nil {
			return err
		}

		if len(resp) == 0 || int32(len(resp)) < pageSize {
			monitors = append(monitors, resp...)
			break
		}

		monitors = append(monitors, resp...)
		pageNumber++
	}

	g.Resources = g.createResources(monitors)
	return nil
}
<commit_msg>Actually filter monitors on tags<commit_after>// Copyright 2018 The Terraformer 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 datadog

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	datadogV1 "github.com/DataDog/datadog-api-client-go/api/v1/datadog"

	"github.com/GoogleCloudPlatform/terraformer/terraformutils"
)

var (
	// MonitorAllowEmptyValues ...
	MonitorAllowEmptyValues = []string{"tags."}
)

// MonitorGenerator ...
type MonitorGenerator struct {
	DatadogService
}

func (g *MonitorGenerator) createResources(monitors []datadogV1.Monitor) []terraformutils.Resource {
	resources := []terraformutils.Resource{}
	for _, monitor := range monitors {
		if monitor.GetType() == datadogV1.MONITORTYPE_SYNTHETICS_ALERT {
			continue
		}
		resourceName := strconv.FormatInt(monitor.GetId(), 10)
		resources = append(resources, g.createResource(resourceName))
	}

	return resources
}

func (g *MonitorGenerator) createResource(monitorID string) terraformutils.Resource {
	return terraformutils.NewSimpleResource(
		monitorID,
		fmt.Sprintf("monitor_%s", monitorID),
		"datadog_monitor",
		"datadog",
		MonitorAllowEmptyValues,
	)
}

// InitResources Generate TerraformResources from Datadog API,
// from each monitor create 1 TerraformResource.
// Need Monitor ID as ID for terraform resource
func (g *MonitorGenerator) InitResources() error {
	datadogClientV1 := g.Args["datadogClientV1"].(*datadogV1.APIClient)
	authV1 := g.Args["authV1"].(context.Context)
	dclient := datadogClientV1.MonitorsApi.ListMonitors(authV1)

	resources := []terraformutils.Resource{}
	for _, filter := range g.Filter {
		if filter.FieldPath == "id" && filter.IsApplicable("monitor") {
			for _, value := range filter.AcceptableValues {
				i, err := strconv.ParseInt(value, 10, 64)
				if err != nil {
					return err
				}

				monitor, _, err := datadogClientV1.MonitorsApi.GetMonitor(authV1, i).Execute()
				if err != nil {
					return err
				}

				resources = append(resources, g.createResource(strconv.FormatInt(monitor.GetId(), 10)))
			}
		}
		if filter.FieldPath == "tags" && filter.IsApplicable("monitor") {
			dclient = dclient.MonitorTags(strings.Join(filter.AcceptableValues, ","))
		}
	}

	if len(resources) > 0 {
		g.Resources = resources
		return nil
	}

	var monitors []datadogV1.Monitor
	pageSize := int32(1000)
	pageNumber := int64(0)
	for {
		resp, _, err := dclient.PageSize(pageSize).Page(pageNumber).Execute()
		if err != nil {
			return err
		}

		if len(resp) == 0 || int32(len(resp)) < pageSize {
			monitors = append(monitors, resp...)
			break
		}

		monitors = append(monitors, resp...)
		pageNumber++
	}

	g.Resources = g.createResources(monitors)
	return nil
}
<|endoftext|>