diff --git a/cloudflare/resource_cloudflare_zone.go b/cloudflare/resource_cloudflare_zone.go index d2d96ea65b..bf28673a0c 100644 --- a/cloudflare/resource_cloudflare_zone.go +++ b/cloudflare/resource_cloudflare_zone.go @@ -3,10 +3,11 @@ package cloudflare import ( "fmt" "log" - "regexp" "strconv" "strings" + "golang.org/x/net/idna" + cloudflare "github.com/cloudflare/cloudflare-go" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" @@ -40,10 +41,10 @@ func resourceCloudflareZone() *schema.Resource { Schema: map[string]*schema.Schema{ "zone": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringMatch(regexp.MustCompile("^([a-zA-Z0-9][\\-a-zA-Z0-9]*\\.)+[\\-a-zA-Z0-9]{2,20}$"), ""), + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: zoneDiffFunc, }, "jump_start": { Type: schema.TypeBool, @@ -264,3 +265,16 @@ func planNameForID(id string) string { } return "" } + +// zoneDiffFunc is a DiffSuppressFunc that accepts two strings and then converts +// them to unicode before performing the comparison whether or not the value has +// changed. This ensures that zones which could be either are evaluated +// consistently and align with what the Cloudflare API returns. +func zoneDiffFunc(k, old, new string, d *schema.ResourceData) bool { + var p *idna.Profile + p = idna.New() + unicodeOld, _ := p.ToUnicode(old) + unicodeNew, _ := p.ToUnicode(new) + + return unicodeOld == unicodeNew +} diff --git a/cloudflare/resource_cloudflare_zone_test.go b/cloudflare/resource_cloudflare_zone_test.go index 58daf8c592..7f121da9c4 100644 --- a/cloudflare/resource_cloudflare_zone_test.go +++ b/cloudflare/resource_cloudflare_zone_test.go @@ -58,10 +58,84 @@ func TestAccCloudflareZone(t *testing.T) { }) } +func TestAccZoneWithUnicodeIsStoredAsUnicode(t *testing.T) { + name := "cloudflare_zone.tf-acc-unicode-test-1" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testZoneConfig("tf-acc-unicode-test-1", "żółw.cfapi.net", "true", "false"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone", "żółw.cfapi.net"), + resource.TestCheckResourceAttr(name, "paused", "true"), + resource.TestCheckResourceAttr(name, "name_servers.#", "2"), + resource.TestCheckResourceAttr(name, "plan", planIDFree), + resource.TestCheckResourceAttr(name, "type", "full"), + ), + }, + }, + }) +} + +func TestAccZoneWithoutUnicodeIsStoredAsUnicode(t *testing.T) { + name := "cloudflare_zone.tf-acc-unicode-test-2" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testZoneConfig("tf-acc-unicode-test-2", "xn--w-uga1v8h.cfapi.net", "true", "false"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone", "żółw.cfapi.net"), + resource.TestCheckResourceAttr(name, "paused", "true"), + resource.TestCheckResourceAttr(name, "name_servers.#", "2"), + resource.TestCheckResourceAttr(name, "plan", planIDFree), + resource.TestCheckResourceAttr(name, "type", "full"), + ), + }, + }, + }) +} + +func TestAccZonePerformsUnicodeComparison(t *testing.T) { + name := "cloudflare_zone.tf-acc-unicode-test-3" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testZoneConfig("tf-acc-unicode-test-3", "żółw.cfapi.net", "true", "false"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone", "żółw.cfapi.net"), + resource.TestCheckResourceAttr(name, "paused", "true"), + resource.TestCheckResourceAttr(name, "name_servers.#", "2"), + resource.TestCheckResourceAttr(name, "plan", planIDFree), + resource.TestCheckResourceAttr(name, "type", "full"), + ), + }, + { + Config: testZoneConfig("tf-acc-unicode-test-3", "xn--w-uga1v8h.cfapi.net", "true", "false"), + PlanOnly: true, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone", "żółw.cfapi.net"), + resource.TestCheckResourceAttr(name, "paused", "true"), + resource.TestCheckResourceAttr(name, "name_servers.#", "2"), + resource.TestCheckResourceAttr(name, "plan", planIDFree), + resource.TestCheckResourceAttr(name, "type", "full"), + ), + }, + }, + }) +} + func testZoneConfig(resourceID, zoneName, paused, jumpStart string) string { return fmt.Sprintf(` resource "cloudflare_zone" "%[1]s" { - zone = "%[2]s" + zone = "%[2]s" paused = %[3]s jump_start = %[4]s }`, resourceID, zoneName, paused, jumpStart) @@ -70,7 +144,7 @@ func testZoneConfig(resourceID, zoneName, paused, jumpStart string) string { func testZoneConfigWithPlan(resourceID, zoneName, paused, jumpStart, plan string) string { return fmt.Sprintf(` resource "cloudflare_zone" "%[1]s" { - zone = "%[2]s" + zone = "%[2]s" paused = %[3]s jump_start = %[4]s plan = "%[5]s" @@ -130,7 +204,7 @@ func TestPlanIDFallsBackToEmptyIfUnknown(t *testing.T) { func testZoneConfigWithPartialSetup(resourceID, zoneName, paused, jumpStart, plan string) string { return fmt.Sprintf(` resource "cloudflare_zone" "%[1]s" { - zone = "%[2]s" + zone = "%[2]s" paused = %[3]s jump_start = %[4]s plan = "%[5]s" @@ -141,7 +215,7 @@ func testZoneConfigWithPartialSetup(resourceID, zoneName, paused, jumpStart, pla func testZoneConfigWithExplicitFullSetup(resourceID, zoneName, paused, jumpStart, plan string) string { return fmt.Sprintf(` resource "cloudflare_zone" "%[1]s" { - zone = "%[2]s" + zone = "%[2]s" paused = %[3]s jump_start = %[4]s plan = "%[5]s" diff --git a/go.mod b/go.mod index d367cff166..8d08a828e8 100644 --- a/go.mod +++ b/go.mod @@ -11,4 +11,5 @@ require ( github.com/hashicorp/terraform v0.12.4 github.com/pkg/errors v0.8.1 google.golang.org/genproto v0.0.0-20190716165318-c506a9f90610 // indirect + golang.org/x/net v0.0.0-20190628185345-da137c7871d7 ) diff --git a/go.sum b/go.sum index 21b08a0c8b..a9b3ee591c 100644 --- a/go.sum +++ b/go.sum @@ -425,6 +425,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190502183928-7f726cade0ab h1:9RfW3ktsOZxgo9YNbBAjq1FWzc/igwEcUzZz8IXgSbk= golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/vendor/golang.org/x/net/http2/server.go b/vendor/golang.org/x/net/http2/server.go index d4abeb2b99..57334dc79b 100644 --- a/vendor/golang.org/x/net/http2/server.go +++ b/vendor/golang.org/x/net/http2/server.go @@ -273,7 +273,20 @@ func ConfigureServer(s *http.Server, conf *Server) error { if testHookOnConn != nil { testHookOnConn() } + // The TLSNextProto interface predates contexts, so + // the net/http package passes down its per-connection + // base context via an exported but unadvertised + // method on the Handler. This is for internal + // net/http<=>http2 use only. + var ctx context.Context + type baseContexter interface { + BaseContext() context.Context + } + if bc, ok := h.(baseContexter); ok { + ctx = bc.BaseContext() + } conf.ServeConn(c, &ServeConnOpts{ + Context: ctx, Handler: h, BaseConfig: hs, }) @@ -284,6 +297,10 @@ func ConfigureServer(s *http.Server, conf *Server) error { // ServeConnOpts are options for the Server.ServeConn method. type ServeConnOpts struct { + // Context is the base context to use. + // If nil, context.Background is used. + Context context.Context + // BaseConfig optionally sets the base configuration // for values. If nil, defaults are used. BaseConfig *http.Server @@ -294,6 +311,13 @@ type ServeConnOpts struct { Handler http.Handler } +func (o *ServeConnOpts) context() context.Context { + if o.Context != nil { + return o.Context + } + return context.Background() +} + func (o *ServeConnOpts) baseConfig() *http.Server { if o != nil && o.BaseConfig != nil { return o.BaseConfig @@ -439,7 +463,7 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) { } func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx context.Context, cancel func()) { - ctx, cancel = context.WithCancel(context.Background()) + ctx, cancel = context.WithCancel(opts.context()) ctx = context.WithValue(ctx, http.LocalAddrContextKey, c.LocalAddr()) if hs := opts.baseConfig(); hs != nil { ctx = context.WithValue(ctx, http.ServerContextKey, hs) diff --git a/vendor/golang.org/x/net/http2/transport.go b/vendor/golang.org/x/net/http2/transport.go index 4ec0792eb5..c0c80d8930 100644 --- a/vendor/golang.org/x/net/http2/transport.go +++ b/vendor/golang.org/x/net/http2/transport.go @@ -28,6 +28,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "time" "golang.org/x/net/http/httpguts" @@ -199,6 +200,7 @@ type ClientConn struct { t *Transport tconn net.Conn // usually *tls.Conn, except specialized impls tlsState *tls.ConnectionState // nil only for specialized impls + reused uint32 // whether conn is being reused; atomic singleUse bool // whether being used for a single http.Request // readLoop goroutine fields: @@ -440,7 +442,8 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err) return nil, err } - traceGotConn(req, cc) + reused := !atomic.CompareAndSwapUint32(&cc.reused, 0, 1) + traceGotConn(req, cc, reused) res, gotErrAfterReqBodyWrite, err := cc.roundTrip(req) if err != nil && retry <= 6 { if req, err = shouldRetryRequest(req, err, gotErrAfterReqBodyWrite); err == nil { @@ -2559,15 +2562,15 @@ func traceGetConn(req *http.Request, hostPort string) { trace.GetConn(hostPort) } -func traceGotConn(req *http.Request, cc *ClientConn) { +func traceGotConn(req *http.Request, cc *ClientConn, reused bool) { trace := httptrace.ContextClientTrace(req.Context()) if trace == nil || trace.GotConn == nil { return } ci := httptrace.GotConnInfo{Conn: cc.tconn} + ci.Reused = reused cc.mu.Lock() - ci.Reused = cc.nextStreamID > 1 - ci.WasIdle = len(cc.streams) == 0 && ci.Reused + ci.WasIdle = len(cc.streams) == 0 && reused if ci.WasIdle && !cc.lastActive.IsZero() { ci.IdleTime = time.Now().Sub(cc.lastActive) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 2849dd839d..37d5995c5c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -267,7 +267,7 @@ golang.org/x/crypto/cast5 golang.org/x/crypto/openpgp/elgamal golang.org/x/crypto/ed25519/internal/edwards25519 golang.org/x/crypto/internal/subtle -# golang.org/x/net v0.0.0-20190502183928-7f726cade0ab +# golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/net/context golang.org/x/net/trace golang.org/x/net/internal/timeseries