Hcl2 input variables, local variables and functions (#8588)
Mainly redefine or reused what Terraform did. * allow to used `variables`, `variable` and `local` blocks * import the following functions and their docs from Terraform: abs, abspath, basename, base64decode, base64encode, bcrypt, can, ceil, chomp, chunklist, cidrhost, cidrnetmask, cidrsubnet, cidrsubnets, coalesce, coalescelist, compact, concat, contains, convert, csvdecode, dirname, distinct, element, file, fileexists, fileset, flatten, floor, format, formatdate, formatlist, indent, index, join, jsondecode, jsonencode, keys, length, log, lookup, lower, max, md5, merge, min, parseint, pathexpand, pow, range, reverse, rsadecrypt, setintersection, setproduct, setunion, sha1, sha256, sha512, signum, slice, sort, split, strrev, substr, timestamp, timeadd, title, trim, trimprefix, trimspace, trimsuffix, try, upper, urlencode, uuidv4, uuidv5, values, yamldecode, yamlencode, zipmap.
This commit is contained in:
parent
8f75fe6e6c
commit
193dad46e6
|
@ -4,4 +4,5 @@
|
|||
*.json text eol=lf
|
||||
*.md text eol=lf
|
||||
*.ps1 text eol=lf
|
||||
*.hcl text eol=lf
|
||||
common/test-fixtures/root/* eol=lf
|
||||
|
|
|
@ -104,7 +104,7 @@ func (c *BuildCommand) GetBuildsFromHCL(path string) ([]packer.Build, int) {
|
|||
PostProcessorsSchemas: c.CoreConfig.Components.PostProcessorStore,
|
||||
}
|
||||
|
||||
builds, diags := parser.Parse(path)
|
||||
builds, diags := parser.Parse(path, c.flagVars)
|
||||
{
|
||||
// write HCL errors/diagnostics if any.
|
||||
b := bytes.NewBuffer(nil)
|
||||
|
|
21
go.mod
21
go.mod
|
@ -57,7 +57,7 @@ require (
|
|||
github.com/google/go-cmp v0.3.1
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9
|
||||
github.com/google/uuid v1.0.0
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gophercloud/gophercloud v0.2.0
|
||||
github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||
|
@ -67,16 +67,21 @@ require (
|
|||
github.com/hashicorp/errwrap v1.0.0
|
||||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0
|
||||
github.com/hashicorp/go-cty-funcs/cidr v0.0.0-20200203151509-c92509f48b18
|
||||
github.com/hashicorp/go-cty-funcs/crypto v0.0.0-20200124154056-476681ae9d62
|
||||
github.com/hashicorp/go-cty-funcs/encoding v0.0.0-20200203151509-c92509f48b18
|
||||
github.com/hashicorp/go-cty-funcs/filesystem v0.0.0-20200203151509-c92509f48b18
|
||||
github.com/hashicorp/go-cty-funcs/uuid v0.0.0-20200203151509-c92509f48b18
|
||||
github.com/hashicorp/go-getter v1.3.1-0.20190906090232-a0f878cb75da
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/hashicorp/go-oracle-terraform v0.0.0-20181016190316-007121241b79
|
||||
github.com/hashicorp/go-retryablehttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.1
|
||||
github.com/hashicorp/go-uuid v1.0.2
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/hashicorp/golang-lru v0.5.3 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl/v2 v2.0.0
|
||||
github.com/hashicorp/hcl/v2 v2.3.0
|
||||
github.com/hashicorp/serf v0.8.2 // indirect
|
||||
github.com/hashicorp/vault v1.1.0
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
|
||||
|
@ -104,9 +109,8 @@ require (
|
|||
github.com/miekg/dns v1.1.1 // indirect
|
||||
github.com/mitchellh/cli v1.0.0
|
||||
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7
|
||||
github.com/mitchellh/go-homedir v1.0.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
|
||||
github.com/mitchellh/gox v1.0.1 // indirect
|
||||
github.com/mitchellh/iochan v1.0.0
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180111000720-b4575eea38cc
|
||||
github.com/mitchellh/panicwrap v0.0.0-20170106182340-fce601fe5557
|
||||
|
@ -147,9 +151,10 @@ require (
|
|||
github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829
|
||||
github.com/zclconf/go-cty v1.1.2-0.20191126233707-f0f7fd24c4af
|
||||
github.com/zclconf/go-cty v1.2.1
|
||||
github.com/zclconf/go-cty-yaml v1.0.1
|
||||
go.opencensus.io v0.22.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587 // indirect
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
|
||||
golang.org/x/mobile v0.0.0-20191130191448-5c0e7e404af8
|
||||
|
@ -173,4 +178,6 @@ replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110
|
|||
|
||||
replace github.com/gofrs/flock => github.com/azr/flock v0.0.0-20190823144736-958d66434653
|
||||
|
||||
replace github.com/zclconf/go-cty => github.com/azr/go-cty v1.1.1-0.20200203143058-28fcda2fe0cc
|
||||
|
||||
go 1.13
|
||||
|
|
42
go.sum
42
go.sum
|
@ -63,6 +63,8 @@ github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 h1:BFFG6KP8ASFBg2pt
|
|||
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M=
|
||||
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apparentlymart/go-cidr v1.0.1 h1:NmIwLZ/KdsjIUlhf+/Np40atNXm/+lZ5txfTJ/SpF+U=
|
||||
github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
||||
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA=
|
||||
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
|
||||
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
|
||||
|
@ -81,12 +83,16 @@ github.com/aws/aws-sdk-go v1.24.1 h1:B2NRyTV1/+h+Dg8Bh7vnuvW6QZz/NBL+uzgC2uILDMI
|
|||
github.com/aws/aws-sdk-go v1.24.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/azr/flock v0.0.0-20190823144736-958d66434653 h1:2H3Cu0cbG8iszfcgnANwC/cm0YkPJIQvaJ9/tSpwh9o=
|
||||
github.com/azr/flock v0.0.0-20190823144736-958d66434653/go.mod h1:EI7lzWWilX2K3ZMZ7Ta+E4DZtWzMC2tbn3cM3oVPuAU=
|
||||
github.com/azr/go-cty v1.1.1-0.20200203143058-28fcda2fe0cc h1:CPIyQPU8jk51LOCR3XUI6hiJHtRqw7fg0kLwCgZPQvs=
|
||||
github.com/azr/go-cty v1.1.1-0.20200203143058-28fcda2fe0cc/go.mod h1:YO23e2L18AG+ZYQfSobnY4G65nvwvprPCxBHkufUH1k=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3 h1:3b+p838vN4sc37brz9W2HDphtSwZFcXZwFLyzm5Vk28=
|
||||
github.com/biogo/hts v0.0.0-20160420073057-50da7d4131a3/go.mod h1:YOY5xnRf7Jz2SZCLSKgVfyqNzbRgyTznM3HyDqQMxcU=
|
||||
github.com/bmatcuk/doublestar v1.1.5 h1:2bNwBOmhyFEFcoB3tGvTD5xanq+4kyOZlB8wFYbMjkk=
|
||||
github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae h1:2Zmk+8cNvAGuY8AyvZuWpUdpQUAXwfom4ReVMe/CTIo=
|
||||
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4=
|
||||
|
@ -196,8 +202,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
|||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 h1:JM174NTeGNJ2m/oLH3UOWOvWQQKd+BoL3hcSCUWFLt0=
|
||||
github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||
github.com/google/uuid v0.0.0-20170306145142-6a5e28554805/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
|
@ -221,6 +227,16 @@ github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSyth
|
|||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cty-funcs/cidr v0.0.0-20200203151509-c92509f48b18 h1:XkNsY3+ulbaCO5Dubd9gpFI9FWRIjRWj7m/dn881cAU=
|
||||
github.com/hashicorp/go-cty-funcs/cidr v0.0.0-20200203151509-c92509f48b18/go.mod h1:0vJoZqJA0f/UCUiRBASXqc1TTlQufBwZpGfXfHAxSNA=
|
||||
github.com/hashicorp/go-cty-funcs/crypto v0.0.0-20200124154056-476681ae9d62 h1:lopgaqFWgt25jxPgadIuZxg1+AMt6NtZ9n2n/SWsR/A=
|
||||
github.com/hashicorp/go-cty-funcs/crypto v0.0.0-20200124154056-476681ae9d62/go.mod h1:e5OaPJuWGKrYY0miHdwWgnlKOtQ+itgsl/AtQqMdaWU=
|
||||
github.com/hashicorp/go-cty-funcs/encoding v0.0.0-20200203151509-c92509f48b18 h1:9njD0lH4TmWPf3RpgW4KVohsgOOZs8eTxO5l2fNZ5bs=
|
||||
github.com/hashicorp/go-cty-funcs/encoding v0.0.0-20200203151509-c92509f48b18/go.mod h1:Q/aJ+s3PMRuuQvDHQLzECc7IdUGT+Q64gxUXa6djhDc=
|
||||
github.com/hashicorp/go-cty-funcs/filesystem v0.0.0-20200203151509-c92509f48b18 h1:VfWkSNJneNfW5gIk8Mi338gTiKeMqhryLDKLx8R5p70=
|
||||
github.com/hashicorp/go-cty-funcs/filesystem v0.0.0-20200203151509-c92509f48b18/go.mod h1:Ax0PN5Le7AV107LPtaMyCzQ8JVcR5uHcSP9ytZj/yv8=
|
||||
github.com/hashicorp/go-cty-funcs/uuid v0.0.0-20200203151509-c92509f48b18 h1:CxYihpdHlBui2AhjjrpfyZ/ulB/SfPaiiiuz6jJm8q8=
|
||||
github.com/hashicorp/go-cty-funcs/uuid v0.0.0-20200203151509-c92509f48b18/go.mod h1:QFbv9KeSic7KIgfOYbUW02G4LxOf3Fh9Ylm4n174LUQ=
|
||||
github.com/hashicorp/go-getter v1.3.1-0.20190906090232-a0f878cb75da h1:HAasZmyRrb7/paYuww5RfVwY3wkFpsbMNYwBxOSZquY=
|
||||
github.com/hashicorp/go-getter v1.3.1-0.20190906090232-a0f878cb75da/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
|
@ -243,7 +259,8 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
|
|||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
||||
|
@ -257,8 +274,8 @@ github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8
|
|||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/hcl/v2 v2.0.0 h1:efQznTz+ydmQXq3BOnRa3AXzvCeTq1P4dKj/z5GLlY8=
|
||||
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
|
||||
github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE=
|
||||
github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
|
||||
|
@ -342,6 +359,8 @@ github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7 h1:PXPMDtfqV+rZJsh
|
|||
github.com/mitchellh/go-fs v0.0.0-20180402234041-7b48fa161ea7/go.mod h1:g7SZj7ABpStq3tM4zqHiVEG5un/DZ1+qJJKO7qx1EvU=
|
||||
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed h1:FI2NIv6fpef6BQl2u3IZX/Cj20tfypRF4yd+uaHOMtI=
|
||||
|
@ -349,8 +368,6 @@ github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaC
|
|||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
|
||||
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
|
||||
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
|
@ -462,10 +479,8 @@ github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e h1:hzwq5G
|
|||
github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829 h1:2FGwbx03GpP1Ulzg/L46tSoKh9t4yg8BhMKQl/Ff1x8=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829/go.mod h1:Eml0jFLU4VVHgIN8zPHMuNwZXVzUMILyO6lQZSfz854=
|
||||
github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw=
|
||||
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v1.1.2-0.20191126233707-f0f7fd24c4af h1:4arg31xOP/qIUV1YVbCWJtChPGzwGzgmlucVbddUq+Y=
|
||||
github.com/zclconf/go-cty v1.1.2-0.20191126233707-f0f7fd24c4af/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
|
||||
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4=
|
||||
|
@ -485,8 +500,8 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU=
|
||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg=
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -514,7 +529,6 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
|
|||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package hcl2template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -10,6 +9,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclparse"
|
||||
. "github.com/hashicorp/packer/hcl2template/internal"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
@ -18,105 +18,23 @@ import (
|
|||
func getBasicParser() *Parser {
|
||||
return &Parser{
|
||||
Parser: hclparse.NewParser(),
|
||||
BuilderSchemas: mapOfBuilder(map[string]packer.Builder{
|
||||
"amazon-ebs": &MockBuilder{},
|
||||
"virtualbox-iso": &MockBuilder{},
|
||||
}),
|
||||
ProvisionersSchemas: mapOfProvisioner(map[string]packer.Provisioner{
|
||||
"shell": &MockProvisioner{},
|
||||
"file": &MockProvisioner{},
|
||||
}),
|
||||
PostProcessorsSchemas: mapOfPostProcessor(map[string]packer.PostProcessor{
|
||||
"amazon-import": &MockPostProcessor{},
|
||||
}),
|
||||
BuilderSchemas: packer.MapOfBuilder{
|
||||
"amazon-ebs": func() (packer.Builder, error) { return &MockBuilder{}, nil },
|
||||
"virtualbox-iso": func() (packer.Builder, error) { return &MockBuilder{}, nil },
|
||||
},
|
||||
ProvisionersSchemas: packer.MapOfProvisioner{
|
||||
"shell": func() (packer.Provisioner, error) { return &MockProvisioner{}, nil },
|
||||
"file": func() (packer.Provisioner, error) { return &MockProvisioner{}, nil },
|
||||
},
|
||||
PostProcessorsSchemas: packer.MapOfPostProcessor{
|
||||
"amazon-import": func() (packer.PostProcessor, error) { return &MockPostProcessor{}, nil },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type mapOfBuilder map[string]packer.Builder
|
||||
|
||||
func (mob mapOfBuilder) Has(builder string) bool {
|
||||
_, res := mob[builder]
|
||||
return res
|
||||
}
|
||||
|
||||
func (mob mapOfBuilder) Start(builder string) (packer.Builder, error) {
|
||||
d, found := mob[builder]
|
||||
var err error
|
||||
if !found {
|
||||
err = fmt.Errorf("Unknown entry %s", builder)
|
||||
}
|
||||
return d, err
|
||||
}
|
||||
|
||||
func (mob mapOfBuilder) List() []string {
|
||||
res := []string{}
|
||||
for k := range mob {
|
||||
res = append(res, k)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type mapOfCommunicator map[string]packer.ConfigurableCommunicator
|
||||
|
||||
func (mob mapOfCommunicator) Start(communicator string) (packer.ConfigurableCommunicator, error) {
|
||||
c, found := mob[communicator]
|
||||
var err error
|
||||
if !found {
|
||||
err = fmt.Errorf("Unknown entry %s", communicator)
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
type mapOfProvisioner map[string]packer.Provisioner
|
||||
|
||||
func (mop mapOfProvisioner) Has(provisioner string) bool {
|
||||
_, res := mop[provisioner]
|
||||
return res
|
||||
}
|
||||
|
||||
func (mop mapOfProvisioner) Start(provisioner string) (packer.Provisioner, error) {
|
||||
p, found := mop[provisioner]
|
||||
var err error
|
||||
if !found {
|
||||
err = fmt.Errorf("Unknown provisioner %s", provisioner)
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (mod mapOfProvisioner) List() []string {
|
||||
res := []string{}
|
||||
for k := range mod {
|
||||
res = append(res, k)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type mapOfPostProcessor map[string]packer.PostProcessor
|
||||
|
||||
func (mop mapOfPostProcessor) Has(provisioner string) bool {
|
||||
_, res := mop[provisioner]
|
||||
return res
|
||||
}
|
||||
|
||||
func (mop mapOfPostProcessor) Start(postProcessor string) (packer.PostProcessor, error) {
|
||||
p, found := mop[postProcessor]
|
||||
var err error
|
||||
if !found {
|
||||
err = fmt.Errorf("Unknown post-processor %s", postProcessor)
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (mod mapOfPostProcessor) List() []string {
|
||||
res := []string{}
|
||||
for k := range mod {
|
||||
res = append(res, k)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type parseTestArgs struct {
|
||||
filename string
|
||||
vars map[string]string
|
||||
}
|
||||
|
||||
type parseTest struct {
|
||||
|
@ -138,7 +56,7 @@ func testParse(t *testing.T, tests []parseTest) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotCfg, gotDiags := tt.parser.parse(tt.args.filename)
|
||||
gotCfg, gotDiags := tt.parser.parse(tt.args.filename, tt.args.vars)
|
||||
if tt.parseWantDiags == (gotDiags == nil) {
|
||||
t.Fatalf("Parser.parse() unexpected diagnostics. %s", gotDiags)
|
||||
}
|
||||
|
@ -146,7 +64,14 @@ func testParse(t *testing.T, tests []parseTest) {
|
|||
t.Fatalf("Parser.parse() unexpected diagnostics HasErrors. %s", gotDiags)
|
||||
}
|
||||
if diff := cmp.Diff(tt.parseWantCfg, gotCfg,
|
||||
cmpopts.IgnoreUnexported(cty.Value{}, Source{}, ProvisionerBlock{}, PostProcessorBlock{}),
|
||||
cmpopts.IgnoreUnexported(
|
||||
cty.Value{},
|
||||
cty.Type{},
|
||||
Variable{},
|
||||
SourceBlock{},
|
||||
ProvisionerBlock{},
|
||||
PostProcessorBlock{},
|
||||
),
|
||||
cmpopts.IgnoreTypes(HCL2Ref{}),
|
||||
cmpopts.IgnoreTypes([]hcl.Range{}),
|
||||
cmpopts.IgnoreTypes(hcl.Range{}),
|
||||
|
@ -164,7 +89,10 @@ func testParse(t *testing.T, tests []parseTest) {
|
|||
t.Fatalf("Parser.getBuilds() unexpected diagnostics. %s", gotDiags)
|
||||
}
|
||||
if diff := cmp.Diff(tt.getBuildsWantBuilds, gotBuilds,
|
||||
cmpopts.IgnoreUnexported(packer.CoreBuild{},
|
||||
cmpopts.IgnoreUnexported(
|
||||
cty.Value{},
|
||||
cty.Type{},
|
||||
packer.CoreBuild{},
|
||||
packer.CoreBuildProvisioner{},
|
||||
packer.CoreBuildPostProcessor{},
|
||||
),
|
||||
|
@ -213,6 +141,7 @@ var (
|
|||
|
||||
basicMockProvisioner = &MockProvisioner{
|
||||
Config: MockConfig{
|
||||
NotSquashed: "value",
|
||||
NestedMockConfig: basicNestedMockConfig,
|
||||
Nested: basicNestedMockConfig,
|
||||
NestedSlice: []NestedMockConfig{
|
||||
|
|
|
@ -6,10 +6,13 @@ import (
|
|||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// Decodable structs are structs that can tell their hcl2 ObjectSpec; this
|
||||
// config spec will be passed to hcldec.Decode and the result will be a
|
||||
// cty.Value. This Value can then be applied on the said struct.
|
||||
type Decodable interface {
|
||||
ConfigSpec() hcldec.ObjectSpec
|
||||
}
|
||||
|
||||
func decodeHCL2Spec(body hcl.Body, ctx *hcl.EvalContext, dec Decodable) (cty.Value, hcl.Diagnostics) {
|
||||
return hcldec.Decode(body, dec.ConfigSpec(), ctx)
|
||||
func decodeHCL2Spec(body hcl.Body, ectx *hcl.EvalContext, dec Decodable) (cty.Value, hcl.Diagnostics) {
|
||||
return hcldec.Decode(body, dec.ConfigSpec(), ectx)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Package hcl2template defines code to parse hcl2 template files correctly.
|
||||
// Package hcl2template defines code to parse hcl2 template files.
|
||||
//
|
||||
// In order to configure a packer builder,provisioner,communicator and post
|
||||
// processor.
|
||||
// In order to configure a packer builder,provisioner, and post processor.
|
||||
//
|
||||
// Checkout the files in testdata/complete/ to see what a packer config could
|
||||
// look like.
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package function
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// TimestampFunc constructs a function that returns a string representation of the current date and time.
|
||||
var TimestampFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return cty.StringVal(time.Now().UTC().Format(time.RFC3339)), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Timestamp returns a string representation of the current date and time.
|
||||
//
|
||||
// In the HCL language, timestamps are conventionally represented as strings
|
||||
// using RFC 3339 "Date and Time format" syntax, and so timestamp returns a
|
||||
// string in this format.
|
||||
func Timestamp() (cty.Value, error) {
|
||||
return TimestampFunc.Call([]cty.Value{})
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package hcl2template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-cty-funcs/cidr"
|
||||
"github.com/hashicorp/go-cty-funcs/crypto"
|
||||
"github.com/hashicorp/go-cty-funcs/encoding"
|
||||
"github.com/hashicorp/go-cty-funcs/filesystem"
|
||||
"github.com/hashicorp/go-cty-funcs/uuid"
|
||||
"github.com/hashicorp/hcl/v2/ext/tryfunc"
|
||||
"github.com/hashicorp/hcl/v2/ext/typeexpr"
|
||||
pkrfunction "github.com/hashicorp/packer/hcl2template/function"
|
||||
ctyyaml "github.com/zclconf/go-cty-yaml"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||||
)
|
||||
|
||||
// Functions returns the set of functions that should be used to when
|
||||
// evaluating expressions in the receiving scope.
|
||||
//
|
||||
// basedir is used with file functions and allows a user to reference a file
|
||||
// using local path. Usually basedir is the directory in which the config file
|
||||
// is located
|
||||
//
|
||||
func Functions(basedir string) map[string]function.Function {
|
||||
|
||||
funcs := map[string]function.Function{
|
||||
"abs": stdlib.AbsoluteFunc,
|
||||
"abspath": filesystem.AbsPathFunc,
|
||||
"basename": filesystem.BasenameFunc,
|
||||
"base64decode": encoding.Base64DecodeFunc,
|
||||
"base64encode": encoding.Base64EncodeFunc,
|
||||
"bcrypt": crypto.BcryptFunc,
|
||||
"can": tryfunc.CanFunc,
|
||||
"ceil": stdlib.CeilFunc,
|
||||
"chomp": stdlib.ChompFunc,
|
||||
"chunklist": stdlib.ChunklistFunc,
|
||||
"cidrhost": cidr.HostFunc,
|
||||
"cidrnetmask": cidr.NetmaskFunc,
|
||||
"cidrsubnet": cidr.SubnetFunc,
|
||||
"cidrsubnets": cidr.SubnetsFunc,
|
||||
"coalesce": stdlib.CoalesceFunc,
|
||||
"coalescelist": stdlib.CoalesceListFunc,
|
||||
"compact": stdlib.CompactFunc,
|
||||
"concat": stdlib.ConcatFunc,
|
||||
"contains": stdlib.ContainsFunc,
|
||||
"convert": typeexpr.ConvertFunc,
|
||||
"csvdecode": stdlib.CSVDecodeFunc,
|
||||
"dirname": filesystem.DirnameFunc,
|
||||
"distinct": stdlib.DistinctFunc,
|
||||
"element": stdlib.ElementFunc,
|
||||
"file": filesystem.MakeFileFunc(basedir, false),
|
||||
"fileexists": filesystem.MakeFileExistsFunc(basedir),
|
||||
"fileset": filesystem.MakeFileSetFunc(basedir),
|
||||
"flatten": stdlib.FlattenFunc,
|
||||
"floor": stdlib.FloorFunc,
|
||||
"format": stdlib.FormatFunc,
|
||||
"formatdate": stdlib.FormatDateFunc,
|
||||
"formatlist": stdlib.FormatListFunc,
|
||||
"indent": stdlib.IndentFunc,
|
||||
"index": stdlib.IndexFunc,
|
||||
"join": stdlib.JoinFunc,
|
||||
"jsondecode": stdlib.JSONDecodeFunc,
|
||||
"jsonencode": stdlib.JSONEncodeFunc,
|
||||
"keys": stdlib.KeysFunc,
|
||||
"length": stdlib.LengthFunc,
|
||||
"log": stdlib.LogFunc,
|
||||
"lookup": stdlib.LookupFunc,
|
||||
"lower": stdlib.LowerFunc,
|
||||
"max": stdlib.MaxFunc,
|
||||
"md5": crypto.Md5Func,
|
||||
"merge": stdlib.MergeFunc,
|
||||
"min": stdlib.MinFunc,
|
||||
"parseint": stdlib.ParseIntFunc,
|
||||
"pathexpand": filesystem.PathExpandFunc,
|
||||
"pow": stdlib.PowFunc,
|
||||
"range": stdlib.RangeFunc,
|
||||
"reverse": stdlib.ReverseFunc,
|
||||
"rsadecrypt": crypto.RsaDecryptFunc,
|
||||
"setintersection": stdlib.SetIntersectionFunc,
|
||||
"setproduct": stdlib.SetProductFunc,
|
||||
"setunion": stdlib.SetUnionFunc,
|
||||
"sha1": crypto.Sha1Func,
|
||||
"sha256": crypto.Sha256Func,
|
||||
"sha512": crypto.Sha512Func,
|
||||
"signum": stdlib.SignumFunc,
|
||||
"slice": stdlib.SliceFunc,
|
||||
"sort": stdlib.SortFunc,
|
||||
"split": stdlib.SplitFunc,
|
||||
"strrev": stdlib.ReverseFunc,
|
||||
"substr": stdlib.SubstrFunc,
|
||||
"timestamp": pkrfunction.TimestampFunc,
|
||||
"timeadd": stdlib.TimeAddFunc,
|
||||
"title": stdlib.TitleFunc,
|
||||
"trim": stdlib.TrimFunc,
|
||||
"trimprefix": stdlib.TrimPrefixFunc,
|
||||
"trimspace": stdlib.TrimSpaceFunc,
|
||||
"trimsuffix": stdlib.TrimSuffixFunc,
|
||||
"try": tryfunc.TryFunc,
|
||||
"upper": stdlib.UpperFunc,
|
||||
"urlencode": encoding.URLEncodeFunc,
|
||||
"uuidv4": uuid.V4Func,
|
||||
"uuidv5": uuid.V5Func,
|
||||
"values": stdlib.ValuesFunc,
|
||||
"yamldecode": ctyyaml.YAMLDecodeFunc,
|
||||
"yamlencode": ctyyaml.YAMLEncodeFunc,
|
||||
"zipmap": stdlib.ZipmapFunc,
|
||||
}
|
||||
|
||||
return funcs
|
||||
}
|
||||
|
||||
var unimplFunc = function.New(&function.Spec{
|
||||
Type: func([]cty.Value) (cty.Type, error) {
|
||||
return cty.DynamicPseudoType, fmt.Errorf("function not yet implemented")
|
||||
},
|
||||
Impl: func([]cty.Value, cty.Type) (cty.Value, error) {
|
||||
return cty.DynamicVal, fmt.Errorf("function not yet implemented")
|
||||
},
|
||||
})
|
|
@ -26,6 +26,7 @@ type NestedMockConfig struct {
|
|||
}
|
||||
|
||||
type MockConfig struct {
|
||||
NotSquashed string `mapstructure:"not_squashed"`
|
||||
NestedMockConfig `mapstructure:",squash"`
|
||||
Nested NestedMockConfig `mapstructure:"nested"`
|
||||
NestedSlice []NestedMockConfig `mapstructure:"nested_slice"`
|
|
@ -9,6 +9,7 @@ import (
|
|||
// FlatMockConfig is an auto-generated flat version of MockConfig.
|
||||
// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up.
|
||||
type FlatMockConfig struct {
|
||||
NotSquashed *string `mapstructure:"not_squashed" cty:"not_squashed"`
|
||||
String *string `mapstructure:"string" cty:"string"`
|
||||
Int *int `mapstructure:"int" cty:"int"`
|
||||
Int64 *int64 `mapstructure:"int64" cty:"int64"`
|
||||
|
@ -36,6 +37,7 @@ func (*MockConfig) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Sp
|
|||
// The decoded values from this spec will then be applied to a FlatMockConfig.
|
||||
func (*FlatMockConfig) HCL2Spec() map[string]hcldec.Spec {
|
||||
s := map[string]hcldec.Spec{
|
||||
"not_squashed": &hcldec.AttrSpec{Name: "not_squashed", Type: cty.String, Required: false},
|
||||
"string": &hcldec.AttrSpec{Name: "string", Type: cty.String, Required: false},
|
||||
"int": &hcldec.AttrSpec{Name: "int", Type: cty.Number, Required: false},
|
||||
"int64": &hcldec.AttrSpec{Name: "int64", Type: cty.Number, Required: false},
|
|
@ -2,6 +2,8 @@ package hcl2template
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclparse"
|
||||
|
@ -11,6 +13,8 @@ import (
|
|||
const (
|
||||
sourceLabel = "source"
|
||||
variablesLabel = "variables"
|
||||
variableLabel = "variable"
|
||||
localsLabel = "locals"
|
||||
buildLabel = "build"
|
||||
communicatorLabel = "communicator"
|
||||
)
|
||||
|
@ -19,11 +23,17 @@ var configSchema = &hcl.BodySchema{
|
|||
Blocks: []hcl.BlockHeaderSchema{
|
||||
{Type: sourceLabel, LabelNames: []string{"type", "name"}},
|
||||
{Type: variablesLabel},
|
||||
{Type: variableLabel, LabelNames: []string{"name"}},
|
||||
{Type: localsLabel},
|
||||
{Type: buildLabel},
|
||||
{Type: communicatorLabel, LabelNames: []string{"type", "name"}},
|
||||
},
|
||||
}
|
||||
|
||||
// Parser helps you parse HCL folders. It will parse an hcl file or directory
|
||||
// and start builders, provisioners and post-processors to configure them with
|
||||
// the parsed HCL and then return a []packer.Build. Packer will use that list
|
||||
// of Builds to run everything in order.
|
||||
type Parser struct {
|
||||
*hclparse.Parser
|
||||
|
||||
|
@ -37,13 +47,26 @@ type Parser struct {
|
|||
const (
|
||||
hcl2FileExt = ".pkr.hcl"
|
||||
hcl2JsonFileExt = ".pkr.json"
|
||||
hcl2VarFileExt = ".auto.pkrvars.hcl"
|
||||
hcl2VarJsonFileExt = ".auto.pkrvars.json"
|
||||
)
|
||||
|
||||
func (p *Parser) parse(filename string) (*PackerConfig, hcl.Diagnostics) {
|
||||
|
||||
hclFiles, jsonFiles, diags := GetHCL2Files(filename)
|
||||
func (p *Parser) parse(filename string, vars map[string]string) (*PackerConfig, hcl.Diagnostics) {
|
||||
|
||||
var files []*hcl.File
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
// parse config files
|
||||
{
|
||||
hclFiles, jsonFiles, moreDiags := GetHCL2Files(filename, hcl2FileExt, hcl2JsonFileExt)
|
||||
if len(hclFiles)+len(jsonFiles) == 0 {
|
||||
diags = append(moreDiags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Could not find any config file in " + filename,
|
||||
Detail: "A config file must be suffixed with `.pkr.hcl` or " +
|
||||
"`.pkr.json`. A folder can be referenced.",
|
||||
})
|
||||
}
|
||||
for _, filename := range hclFiles {
|
||||
f, moreDiags := p.ParseHCLFile(filename)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
@ -57,24 +80,101 @@ func (p *Parser) parse(filename string) (*PackerConfig, hcl.Diagnostics) {
|
|||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &PackerConfig{}
|
||||
basedir := filename
|
||||
if isDir, err := isDir(basedir); err == nil && !isDir {
|
||||
basedir = filepath.Dir(basedir)
|
||||
}
|
||||
cfg := &PackerConfig{
|
||||
Basedir: basedir,
|
||||
}
|
||||
|
||||
// Decode variable blocks so that they are available later on. Here locals
|
||||
// can use input variables so we decode them firsthand.
|
||||
{
|
||||
for _, file := range files {
|
||||
moreDiags := p.parseFile(file, cfg)
|
||||
diags = append(diags, p.decodeInputVariables(file, cfg)...)
|
||||
}
|
||||
for _, file := range files {
|
||||
diags = append(diags, p.decodeLocalVariables(file, cfg)...)
|
||||
}
|
||||
}
|
||||
|
||||
// parse var files
|
||||
{
|
||||
hclVarFiles, jsonVarFiles, moreDiags := GetHCL2Files(filename, hcl2VarFileExt, hcl2VarJsonFileExt)
|
||||
diags = append(diags, moreDiags...)
|
||||
var varFiles []*hcl.File
|
||||
for _, filename := range hclVarFiles {
|
||||
f, moreDiags := p.ParseHCLFile(filename)
|
||||
diags = append(diags, moreDiags...)
|
||||
varFiles = append(varFiles, f)
|
||||
}
|
||||
for _, filename := range jsonVarFiles {
|
||||
f, moreDiags := p.ParseJSONFile(filename)
|
||||
diags = append(diags, moreDiags...)
|
||||
varFiles = append(varFiles, f)
|
||||
}
|
||||
|
||||
diags = append(diags, cfg.InputVariables.collectVariableValues(os.Environ(), varFiles, vars)...)
|
||||
}
|
||||
|
||||
// decode the actual content
|
||||
for _, file := range files {
|
||||
diags = append(diags, p.decodeConfig(file, cfg)...)
|
||||
}
|
||||
|
||||
return cfg, diags
|
||||
}
|
||||
|
||||
// parseFile filename content into cfg.
|
||||
//
|
||||
// parseFile may be called multiple times with the same cfg on a different file.
|
||||
//
|
||||
// parseFile returns as complete a config as we can manage, even if there are
|
||||
// errors, since a partial result can be useful for careful analysis by
|
||||
// development tools such as text editor extensions.
|
||||
func (p *Parser) parseFile(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
|
||||
// decodeLocalVariables looks in the found blocks for 'variables' and
|
||||
// 'variable' blocks. It should be called firsthand so that other blocks can
|
||||
// use the variables.
|
||||
func (p *Parser) decodeInputVariables(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
content, moreDiags := f.Body.Content(configSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
for _, block := range content.Blocks {
|
||||
switch block.Type {
|
||||
case variableLabel:
|
||||
moreDiags := cfg.InputVariables.decodeConfig(block, nil)
|
||||
diags = append(diags, moreDiags...)
|
||||
case variablesLabel:
|
||||
moreDiags := cfg.InputVariables.decodeConfigMap(block, nil)
|
||||
diags = append(diags, moreDiags...)
|
||||
}
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
// decodeLocalVariables looks in the found blocks for 'locals' blocks. It
|
||||
// should be called after parsing input variables so that they can be
|
||||
// referenced.
|
||||
func (p *Parser) decodeLocalVariables(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
content, moreDiags := f.Body.Content(configSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
for _, block := range content.Blocks {
|
||||
switch block.Type {
|
||||
case localsLabel:
|
||||
moreDiags := cfg.LocalVariables.decodeConfigMap(block, cfg.EvalContext())
|
||||
diags = append(diags, moreDiags...)
|
||||
}
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
// decodeConfig looks in the found blocks for everything that is not a variable
|
||||
// block. It should be called after parsing input variables and locals so that
|
||||
// they can be referenced.
|
||||
func (p *Parser) decodeConfig(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
content, moreDiags := f.Body.Content(configSchema)
|
||||
|
@ -104,21 +204,10 @@ func (p *Parser) parseFile(f *hcl.File, cfg *PackerConfig) hcl.Diagnostics {
|
|||
}
|
||||
|
||||
if cfg.Sources == nil {
|
||||
cfg.Sources = map[SourceRef]*Source{}
|
||||
cfg.Sources = map[SourceRef]*SourceBlock{}
|
||||
}
|
||||
cfg.Sources[ref] = source
|
||||
|
||||
case variablesLabel:
|
||||
if cfg.Variables == nil {
|
||||
cfg.Variables = PackerV1Variables{}
|
||||
}
|
||||
|
||||
moreDiags := cfg.Variables.decodeConfig(block)
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
case buildLabel:
|
||||
build, moreDiags := p.decodeBuildConfig(block)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
|
|
@ -7,7 +7,7 @@ build {
|
|||
]
|
||||
|
||||
provisioner "shell" {
|
||||
string = "string"
|
||||
string = lower("STRING")
|
||||
int = 42
|
||||
int64 = 43
|
||||
bool = true
|
||||
|
|
|
@ -8,11 +8,11 @@ build {
|
|||
|
||||
provisioner "shell" {
|
||||
string = "string"
|
||||
int = 42
|
||||
int64 = 43
|
||||
bool = true
|
||||
int = "${41 + 1}"
|
||||
int64 = "${42 + 1}"
|
||||
bool = "true"
|
||||
trilean = true
|
||||
duration = "10s"
|
||||
duration = "${9 + 1}s"
|
||||
map_string_string {
|
||||
a = "b"
|
||||
c = "d"
|
||||
|
|
|
@ -7,21 +7,18 @@ build {
|
|||
|
||||
provisioner "shell" {
|
||||
name = "provisioner that does something"
|
||||
not_squashed = var.foo
|
||||
string = "string"
|
||||
int = 42
|
||||
int64 = 43
|
||||
bool = true
|
||||
int = "${41 + 1}"
|
||||
int64 = "${42 + 1}"
|
||||
bool = "true"
|
||||
trilean = true
|
||||
duration = "10s"
|
||||
duration = "${9 + 1}s"
|
||||
map_string_string {
|
||||
a = "b"
|
||||
c = "d"
|
||||
}
|
||||
slice_string = [
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
]
|
||||
slice_string = var.availability_zone_names
|
||||
slice_slice_string = [
|
||||
["a","b"],
|
||||
["c","d"]
|
||||
|
@ -38,11 +35,7 @@ build {
|
|||
a = "b"
|
||||
c = "d"
|
||||
}
|
||||
slice_string = [
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
]
|
||||
slice_string = var.availability_zone_names
|
||||
slice_slice_string = [
|
||||
["a","b"],
|
||||
["c","d"]
|
||||
|
@ -54,6 +47,7 @@ build {
|
|||
}
|
||||
|
||||
provisioner "file" {
|
||||
not_squashed = "${var.foo}"
|
||||
string = "string"
|
||||
int = 42
|
||||
int64 = 43
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
variables {
|
||||
foo = "value"
|
||||
// my_secret = "foo"
|
||||
// image_name = "foo-image-{{user `my_secret`}}"
|
||||
}
|
||||
|
||||
variable "image_id" {
|
||||
type = string
|
||||
default = "image-id-default"
|
||||
}
|
||||
|
||||
variable "port" {
|
||||
type = number
|
||||
default = 42
|
||||
}
|
||||
|
||||
variable "availability_zone_names" {
|
||||
type = list(string)
|
||||
default = ["a", "b", "c"]
|
||||
}
|
||||
|
||||
locals {
|
||||
feefoo = "${var.foo}_${var.image_id}"
|
||||
}
|
|
@ -4,3 +4,34 @@ variables {
|
|||
my_secret = "foo"
|
||||
image_name = "foo-image-{{user `my_secret`}}"
|
||||
}
|
||||
|
||||
variable "image_id" {
|
||||
type = string
|
||||
default = "image-id-default"
|
||||
}
|
||||
|
||||
variable "port" {
|
||||
type = number
|
||||
default = 42
|
||||
}
|
||||
|
||||
variable "availability_zone_names" {
|
||||
type = list(string)
|
||||
default = ["us-west-1a"]
|
||||
description = <<POTATO
|
||||
Describing is awesome ;D
|
||||
POTATO
|
||||
}
|
||||
|
||||
variable "super_secret_password" {
|
||||
type = string
|
||||
sensitive = true
|
||||
description = <<IMSENSIBLE
|
||||
Handle with care plz
|
||||
IMSENSIBLE
|
||||
}
|
||||
|
||||
locals {
|
||||
service_name = "forum"
|
||||
owner = "Community Team"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
variable "boolean_value" {
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "boolean_value" {
|
||||
default = true
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
variables {
|
||||
boolean_value = false
|
||||
}
|
||||
|
||||
variables {
|
||||
boolean_value = true
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
|
||||
variable "broken_type" {
|
||||
type = list(string)
|
||||
default = true
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
variable "broken_type" {
|
||||
invalid = true
|
||||
}
|
|
@ -24,18 +24,34 @@ var buildSchema = &hcl.BodySchema{
|
|||
},
|
||||
}
|
||||
|
||||
// BuildBlock references an HCL 'build' block and it content, for example :
|
||||
//
|
||||
// build {
|
||||
// sources = [
|
||||
// ...
|
||||
// ]
|
||||
// provisioner "" { ... }
|
||||
// post-processor "" { ... }
|
||||
// }
|
||||
type BuildBlock struct {
|
||||
// Sources is the list of sources that we want to start in this build block.
|
||||
Sources []SourceRef
|
||||
|
||||
// ProvisionerBlocks references a list of HCL provisioner block that will
|
||||
// will be ran against the sources.
|
||||
ProvisionerBlocks []*ProvisionerBlock
|
||||
|
||||
// ProvisionerBlocks references a list of HCL post-processors block that
|
||||
// will be ran against the artifacts from the provisioning steps.
|
||||
PostProcessors []*PostProcessorBlock
|
||||
|
||||
Froms []SourceRef
|
||||
|
||||
HCL2Ref HCL2Ref
|
||||
}
|
||||
|
||||
type Builds []*BuildBlock
|
||||
|
||||
// decodeBuildConfig is called when a 'build' block has been detected. It will
|
||||
// load the references to the contents of the build block.
|
||||
func (p *Parser) decodeBuildConfig(block *hcl.Block) (*BuildBlock, hcl.Diagnostics) {
|
||||
build := &BuildBlock{}
|
||||
|
||||
|
@ -63,7 +79,7 @@ func (p *Parser) decodeBuildConfig(block *hcl.Block) (*BuildBlock, hcl.Diagnosti
|
|||
continue
|
||||
}
|
||||
|
||||
build.Froms = append(build.Froms, ref)
|
||||
build.Sources = append(build.Sources, ref)
|
||||
}
|
||||
|
||||
content, moreDiags := b.Config.Content(buildSchema)
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// PostProcessorBlock represents a parsed PostProcessorBlock
|
||||
// ProvisionerBlock references a detected but unparsed post processor
|
||||
type PostProcessorBlock struct {
|
||||
PType string
|
||||
PName string
|
||||
|
@ -49,7 +49,8 @@ func (p *Parser) decodePostProcessor(block *hcl.Block) (*PostProcessorBlock, hcl
|
|||
return postProcessor, diags
|
||||
}
|
||||
|
||||
func (p *Parser) StartPostProcessor(pp *PostProcessorBlock) (packer.PostProcessor, hcl.Diagnostics) {
|
||||
func (p *Parser) startPostProcessor(pp *PostProcessorBlock, ectx *hcl.EvalContext) (packer.PostProcessor, hcl.Diagnostics) {
|
||||
// ProvisionerBlock represents a detected but unparsed provisioner
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
postProcessor, err := p.PostProcessorsSchemas.Start(pp.PType)
|
||||
|
@ -61,7 +62,7 @@ func (p *Parser) StartPostProcessor(pp *PostProcessorBlock) (packer.PostProcesso
|
|||
})
|
||||
return nil, diags
|
||||
}
|
||||
flatProvisinerCfg, moreDiags := decodeHCL2Spec(pp.Rest, nil, postProcessor)
|
||||
flatProvisinerCfg, moreDiags := decodeHCL2Spec(pp.Rest, ectx, postProcessor)
|
||||
diags = append(diags, moreDiags...)
|
||||
err = postProcessor.Configure(flatProvisinerCfg)
|
||||
if err != nil {
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// ProvisionerBlock represents a parsed provisioner
|
||||
// ProvisionerBlock references a detected but unparsed provisioner
|
||||
type ProvisionerBlock struct {
|
||||
PType string
|
||||
PName string
|
||||
|
@ -47,7 +47,7 @@ func (p *Parser) decodeProvisioner(block *hcl.Block) (*ProvisionerBlock, hcl.Dia
|
|||
return provisioner, diags
|
||||
}
|
||||
|
||||
func (p *Parser) StartProvisioner(pb *ProvisionerBlock, generatedVars []string) (packer.Provisioner, hcl.Diagnostics) {
|
||||
func (p *Parser) startProvisioner(pb *ProvisionerBlock, ectx *hcl.EvalContext, generatedVars []string) (packer.Provisioner, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
provisioner, err := p.ProvisionersSchemas.Start(pb.PType)
|
||||
|
@ -59,7 +59,7 @@ func (p *Parser) StartProvisioner(pb *ProvisionerBlock, generatedVars []string)
|
|||
})
|
||||
return nil, diags
|
||||
}
|
||||
flatProvisionerCfg, moreDiags := decodeHCL2Spec(pb.HCL2Ref.Rest, nil, provisioner)
|
||||
flatProvisionerCfg, moreDiags := decodeHCL2Spec(pb.HCL2Ref.Rest, ectx, provisioner)
|
||||
diags = append(diags, moreDiags...)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package hcl2template
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
@ -12,11 +13,12 @@ func TestParse_build(t *testing.T) {
|
|||
tests := []parseTest{
|
||||
{"basic build no src",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/build/basic.pkr.hcl"},
|
||||
parseTestArgs{"testdata/build/basic.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "build"),
|
||||
Builds: Builds{
|
||||
&BuildBlock{
|
||||
Froms: []SourceRef{
|
||||
Sources: []SourceRef{
|
||||
{
|
||||
Type: "amazon-ebs",
|
||||
Name: "ubuntu-1604",
|
||||
|
@ -45,8 +47,9 @@ func TestParse_build(t *testing.T) {
|
|||
},
|
||||
{"untyped provisioner",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/build/provisioner_untyped.pkr.hcl"},
|
||||
parseTestArgs{"testdata/build/provisioner_untyped.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "build"),
|
||||
Builds: nil,
|
||||
},
|
||||
true, true,
|
||||
|
@ -55,8 +58,9 @@ func TestParse_build(t *testing.T) {
|
|||
},
|
||||
{"inexistent provisioner",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/build/provisioner_inexistent.pkr.hcl"},
|
||||
parseTestArgs{"testdata/build/provisioner_inexistent.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "build"),
|
||||
Builds: nil,
|
||||
},
|
||||
true, true,
|
||||
|
@ -65,8 +69,9 @@ func TestParse_build(t *testing.T) {
|
|||
},
|
||||
{"untyped post-processor",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/build/post-processor_untyped.pkr.hcl"},
|
||||
parseTestArgs{"testdata/build/post-processor_untyped.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "build"),
|
||||
Builds: nil,
|
||||
},
|
||||
true, true,
|
||||
|
@ -75,8 +80,9 @@ func TestParse_build(t *testing.T) {
|
|||
},
|
||||
{"inexistent post-processor",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/build/post-processor_inexistent.pkr.hcl"},
|
||||
parseTestArgs{"testdata/build/post-processor_inexistent.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "build"),
|
||||
Builds: nil,
|
||||
},
|
||||
true, true,
|
||||
|
@ -85,8 +91,9 @@ func TestParse_build(t *testing.T) {
|
|||
},
|
||||
{"invalid source",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/build/invalid_source_reference.pkr.hcl"},
|
||||
parseTestArgs{"testdata/build/invalid_source_reference.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "build"),
|
||||
Builds: nil,
|
||||
},
|
||||
true, true,
|
||||
|
|
|
@ -4,9 +4,12 @@ import (
|
|||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// reference to the source definition in configuration text file
|
||||
// HCL2Ref references to the source definition in configuration text file. It
|
||||
// is used to tell were something was wrong, - like a warning or an error -
|
||||
// long after it was parsed; allowing to give pointers as to where change/fix
|
||||
// things in a file.
|
||||
type HCL2Ref struct {
|
||||
// reference to the source definition in configuration text file
|
||||
// references
|
||||
DefRange hcl.Range
|
||||
TypeRange hcl.Range
|
||||
LabelsRanges []hcl.Range
|
||||
|
|
|
@ -3,22 +3,50 @@ package hcl2template
|
|||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// PackerConfig represents a loaded packer config
|
||||
// PackerConfig represents a loaded Packer HCL config. It will contain
|
||||
// references to all possible blocks of the allowed configuration.
|
||||
type PackerConfig struct {
|
||||
Sources map[SourceRef]*Source
|
||||
// Directory where the config files are defined
|
||||
Basedir string
|
||||
|
||||
Variables PackerV1Variables
|
||||
// Available Source blocks
|
||||
Sources map[SourceRef]*SourceBlock
|
||||
|
||||
// InputVariables and LocalVariables are the list of defined input and
|
||||
// local variables. They are of the same type but are not used in the same
|
||||
// way. Local variables will not be decoded from any config file, env var,
|
||||
// or ect. Like the Input variables will.
|
||||
InputVariables Variables
|
||||
LocalVariables Variables
|
||||
|
||||
// Builds is the list of Build blocks defined in the config files.
|
||||
Builds Builds
|
||||
}
|
||||
|
||||
func (p *Parser) CoreBuildProvisioners(blocks []*ProvisionerBlock, generatedVars []string) ([]packer.CoreBuildProvisioner, hcl.Diagnostics) {
|
||||
// EvalContext returns the *hcl.EvalContext that will be passed to an hcl
|
||||
// decoder in order to tell what is the actual value of a var or a local and
|
||||
// the list of defined functions.
|
||||
func (cfg *PackerConfig) EvalContext() *hcl.EvalContext {
|
||||
ectx := &hcl.EvalContext{
|
||||
Functions: Functions(cfg.Basedir),
|
||||
Variables: map[string]cty.Value{
|
||||
"var": cty.ObjectVal(cfg.InputVariables.Values()),
|
||||
"local": cty.ObjectVal(cfg.LocalVariables.Values()),
|
||||
},
|
||||
}
|
||||
return ectx
|
||||
}
|
||||
|
||||
// getCoreBuildProvisioners takes a list of provisioner block, starts according
|
||||
// provisioners and sends parsed HCL2 over to it.
|
||||
func (p *Parser) getCoreBuildProvisioners(blocks []*ProvisionerBlock, ectx *hcl.EvalContext, generatedVars []string) ([]packer.CoreBuildProvisioner, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
res := []packer.CoreBuildProvisioner{}
|
||||
for _, pb := range blocks {
|
||||
provisioner, moreDiags := p.StartProvisioner(pb, generatedVars)
|
||||
provisioner, moreDiags := p.startProvisioner(pb, ectx, generatedVars)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
|
@ -32,11 +60,13 @@ func (p *Parser) CoreBuildProvisioners(blocks []*ProvisionerBlock, generatedVars
|
|||
return res, diags
|
||||
}
|
||||
|
||||
func (p *Parser) CoreBuildPostProcessors(blocks []*PostProcessorBlock) ([]packer.CoreBuildPostProcessor, hcl.Diagnostics) {
|
||||
// getCoreBuildProvisioners takes a list of post processor block, starts
|
||||
// according provisioners and sends parsed HCL2 over to it.
|
||||
func (p *Parser) getCoreBuildPostProcessors(blocks []*PostProcessorBlock, ectx *hcl.EvalContext) ([]packer.CoreBuildPostProcessor, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
res := []packer.CoreBuildPostProcessor{}
|
||||
for _, ppb := range blocks {
|
||||
postProcessor, moreDiags := p.StartPostProcessor(ppb)
|
||||
postProcessor, moreDiags := p.startPostProcessor(ppb, ectx)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
|
@ -51,12 +81,15 @@ func (p *Parser) CoreBuildPostProcessors(blocks []*PostProcessorBlock) ([]packer
|
|||
return res, diags
|
||||
}
|
||||
|
||||
// getBuilds will return a list of packer Build based on the HCL2 parsed build
|
||||
// blocks. All Builders, Provisioners and Post Processors will be started and
|
||||
// configured.
|
||||
func (p *Parser) getBuilds(cfg *PackerConfig) ([]packer.Build, hcl.Diagnostics) {
|
||||
res := []packer.Build{}
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
for _, build := range cfg.Builds {
|
||||
for _, from := range build.Froms {
|
||||
for _, from := range build.Sources {
|
||||
src, found := cfg.Sources[from]
|
||||
if !found {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
|
@ -66,17 +99,17 @@ func (p *Parser) getBuilds(cfg *PackerConfig) ([]packer.Build, hcl.Diagnostics)
|
|||
})
|
||||
continue
|
||||
}
|
||||
builder, moreDiags, generatedVars := p.StartBuilder(src)
|
||||
builder, moreDiags, generatedVars := p.startBuilder(src, cfg.EvalContext())
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
provisioners, moreDiags := p.CoreBuildProvisioners(build.ProvisionerBlocks, generatedVars)
|
||||
provisioners, moreDiags := p.getCoreBuildProvisioners(build.ProvisionerBlocks, cfg.EvalContext(), generatedVars)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
postProcessors, moreDiags := p.CoreBuildPostProcessors(build.PostProcessors)
|
||||
postProcessors, moreDiags := p.getCoreBuildPostProcessors(build.PostProcessors, cfg.EvalContext())
|
||||
pps := [][]packer.CoreBuildPostProcessor{}
|
||||
if len(postProcessors) > 0 {
|
||||
pps = [][]packer.CoreBuildPostProcessor{postProcessors}
|
||||
|
@ -91,7 +124,6 @@ func (p *Parser) getBuilds(cfg *PackerConfig) ([]packer.Build, hcl.Diagnostics)
|
|||
Builder: builder,
|
||||
Provisioners: provisioners,
|
||||
PostProcessors: pps,
|
||||
Variables: cfg.Variables,
|
||||
}
|
||||
res = append(res, pcb)
|
||||
}
|
||||
|
@ -99,8 +131,18 @@ func (p *Parser) getBuilds(cfg *PackerConfig) ([]packer.Build, hcl.Diagnostics)
|
|||
return res, diags
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(path string) ([]packer.Build, hcl.Diagnostics) {
|
||||
cfg, diags := p.parse(path)
|
||||
// Parse will parse HCL file(s) in path. Path can be a folder or a file.
|
||||
//
|
||||
// Parse will first parse variables and then the rest; so that interpolation
|
||||
// can happen.
|
||||
//
|
||||
// For each build block a packer.Build will be started, and for each builder,
|
||||
// all provisioners and post-processors will be started.
|
||||
//
|
||||
// Parse then return a slice of packer.Builds; which are what packer core uses
|
||||
// to run builds.
|
||||
func (p *Parser) Parse(path string, vars map[string]string) ([]packer.Build, hcl.Diagnostics) {
|
||||
cfg, diags := p.parse(path, vars)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
|
|
@ -17,14 +17,24 @@ func TestParser_complete(t *testing.T) {
|
|||
tests := []parseTest{
|
||||
{"working build",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/complete"},
|
||||
parseTestArgs{"testdata/complete", nil},
|
||||
&PackerConfig{
|
||||
Sources: map[SourceRef]*Source{
|
||||
Basedir: "testdata/complete",
|
||||
InputVariables: Variables{
|
||||
"foo": &Variable{},
|
||||
"image_id": &Variable{},
|
||||
"port": &Variable{},
|
||||
"availability_zone_names": &Variable{},
|
||||
},
|
||||
LocalVariables: Variables{
|
||||
"feefoo": &Variable{},
|
||||
},
|
||||
Sources: map[SourceRef]*SourceBlock{
|
||||
refVBIsoUbuntu1204: {Type: "virtualbox-iso", Name: "ubuntu-1204"},
|
||||
},
|
||||
Builds: Builds{
|
||||
&BuildBlock{
|
||||
Froms: []SourceRef{refVBIsoUbuntu1204},
|
||||
Sources: []SourceRef{refVBIsoUbuntu1204},
|
||||
ProvisionerBlocks: []*ProvisionerBlock{
|
||||
{
|
||||
PType: "shell",
|
||||
|
@ -76,7 +86,7 @@ func TestParser_complete(t *testing.T) {
|
|||
},
|
||||
{"dir with no config files",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/empty"},
|
||||
parseTestArgs{"testdata/empty", nil},
|
||||
nil,
|
||||
true, true,
|
||||
nil,
|
||||
|
@ -84,18 +94,19 @@ func TestParser_complete(t *testing.T) {
|
|||
},
|
||||
{name: "inexistent dir",
|
||||
parser: defaultParser,
|
||||
args: parseTestArgs{"testdata/inexistent"},
|
||||
args: parseTestArgs{"testdata/inexistent", nil},
|
||||
parseWantCfg: nil,
|
||||
parseWantDiags: true,
|
||||
parseWantDiagHasErrors: true,
|
||||
},
|
||||
{name: "folder named build.pkr.hcl with an unknown src",
|
||||
parser: defaultParser,
|
||||
args: parseTestArgs{"testdata/build.pkr.hcl"},
|
||||
args: parseTestArgs{"testdata/build.pkr.hcl", nil},
|
||||
parseWantCfg: &PackerConfig{
|
||||
Basedir: "testdata/build.pkr.hcl",
|
||||
Builds: Builds{
|
||||
&BuildBlock{
|
||||
Froms: []SourceRef{refAWSEBSUbuntu1204, refVBIsoUbuntu1204},
|
||||
Sources: []SourceRef{refAWSEBSUbuntu1204, refVBIsoUbuntu1204},
|
||||
ProvisionerBlocks: []*ProvisionerBlock{
|
||||
{PType: "shell"},
|
||||
{PType: "file"},
|
||||
|
@ -113,8 +124,10 @@ func TestParser_complete(t *testing.T) {
|
|||
},
|
||||
{name: "unknown block type",
|
||||
parser: defaultParser,
|
||||
args: parseTestArgs{"testdata/unknown"},
|
||||
parseWantCfg: &PackerConfig{},
|
||||
args: parseTestArgs{"testdata/unknown", nil},
|
||||
parseWantCfg: &PackerConfig{
|
||||
Basedir: "testdata/unknown",
|
||||
},
|
||||
parseWantDiags: true,
|
||||
parseWantDiagHasErrors: true,
|
||||
},
|
||||
|
|
|
@ -7,9 +7,8 @@ import (
|
|||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// A source field in an HCL file will load into the Source type.
|
||||
//
|
||||
type Source struct {
|
||||
// SourceBlock references an HCL 'source' block.
|
||||
type SourceBlock struct {
|
||||
// Type of source; ex: virtualbox-iso
|
||||
Type string
|
||||
// Given name; if any
|
||||
|
@ -18,8 +17,8 @@ type Source struct {
|
|||
block *hcl.Block
|
||||
}
|
||||
|
||||
func (p *Parser) decodeSource(block *hcl.Block) (*Source, hcl.Diagnostics) {
|
||||
source := &Source{
|
||||
func (p *Parser) decodeSource(block *hcl.Block) (*SourceBlock, hcl.Diagnostics) {
|
||||
source := &SourceBlock{
|
||||
Type: block.Labels[0],
|
||||
Name: block.Labels[1],
|
||||
block: block,
|
||||
|
@ -39,7 +38,7 @@ func (p *Parser) decodeSource(block *hcl.Block) (*Source, hcl.Diagnostics) {
|
|||
return source, diags
|
||||
}
|
||||
|
||||
func (p *Parser) StartBuilder(source *Source) (packer.Builder, hcl.Diagnostics, []string) {
|
||||
func (p *Parser) startBuilder(source *SourceBlock, ectx *hcl.EvalContext) (packer.Builder, hcl.Diagnostics, []string) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
builder, err := p.BuilderSchemas.Start(source.Type)
|
||||
|
@ -52,7 +51,7 @@ func (p *Parser) StartBuilder(source *Source) (packer.Builder, hcl.Diagnostics,
|
|||
return builder, diags, nil
|
||||
}
|
||||
|
||||
decoded, moreDiags := decodeHCL2Spec(source.block.Body, nil, builder)
|
||||
decoded, moreDiags := decodeHCL2Spec(source.block.Body, ectx, builder)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
return nil, diags, nil
|
||||
|
@ -64,7 +63,7 @@ func (p *Parser) StartBuilder(source *Source) (packer.Builder, hcl.Diagnostics,
|
|||
return builder, diags, generatedVars
|
||||
}
|
||||
|
||||
func (source *Source) Ref() SourceRef {
|
||||
func (source *SourceBlock) Ref() SourceRef {
|
||||
return SourceRef{
|
||||
Type: source.Type,
|
||||
Name: source.Name,
|
||||
|
@ -80,54 +79,6 @@ type SourceRef struct {
|
|||
// source.
|
||||
var NoSource SourceRef
|
||||
|
||||
func sourceRefFromAbsTraversal(t hcl.Traversal) (SourceRef, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
if len(t) != 3 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid " + sourceLabel + " reference",
|
||||
Detail: "A " + sourceLabel + " reference must have three parts separated by periods: the keyword \"" + sourceLabel + "\", the builder type name, and the source name.",
|
||||
Subject: t.SourceRange().Ptr(),
|
||||
})
|
||||
return NoSource, diags
|
||||
}
|
||||
|
||||
if t.RootName() != sourceLabel {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid " + sourceLabel + " reference",
|
||||
Detail: "The first part of an source reference must be the keyword \"" + sourceLabel + "\".",
|
||||
Subject: t[0].SourceRange().Ptr(),
|
||||
})
|
||||
return NoSource, diags
|
||||
}
|
||||
btStep, ok := t[1].(hcl.TraverseAttr)
|
||||
if !ok {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid " + sourceLabel + " reference",
|
||||
Detail: "The second part of an " + sourceLabel + " reference must be an identifier giving the builder type of the " + sourceLabel + ".",
|
||||
Subject: t[1].SourceRange().Ptr(),
|
||||
})
|
||||
return NoSource, diags
|
||||
}
|
||||
nameStep, ok := t[2].(hcl.TraverseAttr)
|
||||
if !ok {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid " + sourceLabel + " reference",
|
||||
Detail: "The third part of an " + sourceLabel + " reference must be an identifier giving the name of the " + sourceLabel + ".",
|
||||
Subject: t[2].SourceRange().Ptr(),
|
||||
})
|
||||
return NoSource, diags
|
||||
}
|
||||
|
||||
return SourceRef{
|
||||
Type: btStep.Name,
|
||||
Name: nameStep.Name,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func (r SourceRef) String() string {
|
||||
return fmt.Sprintf("%s.%s", r.Type, r.Name)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package hcl2template
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
@ -12,9 +13,10 @@ func TestParse_source(t *testing.T) {
|
|||
tests := []parseTest{
|
||||
{"two basic sources",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/sources/basic.pkr.hcl"},
|
||||
parseTestArgs{"testdata/sources/basic.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Sources: map[SourceRef]*Source{
|
||||
Basedir: filepath.Join("testdata", "sources"),
|
||||
Sources: map[SourceRef]*SourceBlock{
|
||||
{
|
||||
Type: "virtualbox-iso",
|
||||
Name: "ubuntu-1204",
|
||||
|
@ -30,33 +32,40 @@ func TestParse_source(t *testing.T) {
|
|||
},
|
||||
{"untyped source",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/sources/untyped.pkr.hcl"},
|
||||
&PackerConfig{},
|
||||
parseTestArgs{"testdata/sources/untyped.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "sources"),
|
||||
},
|
||||
true, true,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{"unnamed source",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/sources/unnamed.pkr.hcl"},
|
||||
&PackerConfig{},
|
||||
parseTestArgs{"testdata/sources/unnamed.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "sources"),
|
||||
},
|
||||
true, true,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{"inexistent source",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/sources/inexistent.pkr.hcl"},
|
||||
&PackerConfig{},
|
||||
parseTestArgs{"testdata/sources/inexistent.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "sources"),
|
||||
},
|
||||
true, true,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{"duplicate source",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/sources/duplicate.pkr.hcl"},
|
||||
parseTestArgs{"testdata/sources/duplicate.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Sources: map[SourceRef]*Source{
|
||||
Basedir: filepath.Join("testdata", "sources"),
|
||||
Sources: map[SourceRef]*SourceBlock{
|
||||
{
|
||||
Type: "virtualbox-iso",
|
||||
Name: "ubuntu-1204",
|
||||
|
|
|
@ -1,27 +1,373 @@
|
|||
package hcl2template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/ext/typeexpr"
|
||||
"github.com/hashicorp/hcl/v2/gohcl"
|
||||
"github.com/hashicorp/packer/template"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
)
|
||||
|
||||
type PackerV1Variables map[string]string
|
||||
type Variable struct {
|
||||
// CmdValue, VarfileValue, EnvValue, DefaultValue are possible values of
|
||||
// the variable; The first value set from these will be the one used. If
|
||||
// none is set; an error will be returned if a user tries to use the
|
||||
// Variable.
|
||||
CmdValue cty.Value
|
||||
VarfileValue cty.Value
|
||||
EnvValue cty.Value
|
||||
DefaultValue cty.Value
|
||||
|
||||
// decodeConfig decodes a "variables" section the way packer 1 used to
|
||||
func (variables *PackerV1Variables) decodeConfig(block *hcl.Block) hcl.Diagnostics {
|
||||
return gohcl.DecodeBody(block.Body, nil, variables)
|
||||
// Cty Type of the variable. If the default value or a collected value is
|
||||
// not of this type nor can be converted to this type an error diagnostic
|
||||
// will show up. This allows us to assume that values are valid later in
|
||||
// code.
|
||||
//
|
||||
// When a default value - and no type - is passed in the variable
|
||||
// declaration, the type of the default variable will be used. This will
|
||||
// allow to ensure that users set this variable correctly.
|
||||
Type cty.Type
|
||||
// Description of the variable
|
||||
Description string
|
||||
// When Sensitive is set to true Packer will try it best to hide/obfuscate
|
||||
// the variable from the output stream. By replacing the text.
|
||||
Sensitive bool
|
||||
|
||||
block *hcl.Block
|
||||
}
|
||||
|
||||
func (variables PackerV1Variables) Variables() map[string]*template.Variable {
|
||||
res := map[string]*template.Variable{}
|
||||
func (v *Variable) GoString() string {
|
||||
return fmt.Sprintf("{Type:%q,CmdValue:%q,VarfileValue:%q,EnvValue:%q,DefaultValue:%q}",
|
||||
v.Type.GoString(), v.CmdValue.GoString(), v.VarfileValue.GoString(), v.EnvValue.GoString(), v.DefaultValue.GoString())
|
||||
}
|
||||
|
||||
func (v *Variable) Value() (cty.Value, *hcl.Diagnostic) {
|
||||
for _, value := range []cty.Value{
|
||||
v.CmdValue,
|
||||
v.VarfileValue,
|
||||
v.EnvValue,
|
||||
v.DefaultValue,
|
||||
} {
|
||||
if !value.IsNull() {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
return cty.NilVal, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unset variable",
|
||||
Detail: "A used variable must be set; see " +
|
||||
"https://packer.io/docs/configuration/from-1.5/syntax.html for details.",
|
||||
Context: v.block.DefRange.Ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
type Variables map[string]*Variable
|
||||
|
||||
func (variables Variables) Values() map[string]cty.Value {
|
||||
res := map[string]cty.Value{}
|
||||
for k, v := range variables {
|
||||
res[k] = &template.Variable{
|
||||
Key: k,
|
||||
Default: v,
|
||||
res[k], _ = v.Value()
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// decodeConfig decodes a "variables" section the way packer 1 used to
|
||||
func (variables *Variables) decodeConfigMap(block *hcl.Block, ectx *hcl.EvalContext) hcl.Diagnostics {
|
||||
if (*variables) == nil {
|
||||
(*variables) = Variables{}
|
||||
}
|
||||
attrs, diags := block.Body.JustAttributes()
|
||||
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
for key, attr := range attrs {
|
||||
if _, found := (*variables)[key]; found {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Duplicate variable",
|
||||
Detail: "Duplicate " + key + " variable definition found.",
|
||||
Subject: attr.NameRange.Ptr(),
|
||||
Context: block.DefRange.Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
value, moreDiags := attr.Expr.Value(ectx)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
(*variables)[key] = &Variable{
|
||||
DefaultValue: value,
|
||||
Type: value.Type(),
|
||||
}
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
// decodeConfig decodes a "variables" section the way packer 1 used to
|
||||
func (variables *Variables) decodeConfig(block *hcl.Block, ectx *hcl.EvalContext) hcl.Diagnostics {
|
||||
if (*variables) == nil {
|
||||
(*variables) = Variables{}
|
||||
}
|
||||
|
||||
if _, found := (*variables)[block.Labels[0]]; found {
|
||||
|
||||
return []*hcl.Diagnostic{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Duplicate variable",
|
||||
Detail: "Duplicate " + block.Labels[0] + " variable definition found.",
|
||||
Context: block.DefRange.Ptr(),
|
||||
}}
|
||||
}
|
||||
|
||||
var b struct {
|
||||
Description string `hcl:"description,optional"`
|
||||
Sensitive bool `hcl:"sensitive,optional"`
|
||||
Rest hcl.Body `hcl:",remain"`
|
||||
}
|
||||
diags := gohcl.DecodeBody(block.Body, nil, &b)
|
||||
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
res := &Variable{
|
||||
Description: b.Description,
|
||||
Sensitive: b.Sensitive,
|
||||
block: block,
|
||||
}
|
||||
|
||||
attrs, moreDiags := b.Rest.JustAttributes()
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
if t, ok := attrs["type"]; ok {
|
||||
delete(attrs, "type")
|
||||
tp, moreDiags := typeexpr.Type(t.Expr)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
res.Type = tp
|
||||
delete(attrs, "type")
|
||||
}
|
||||
|
||||
if def, ok := attrs["default"]; ok {
|
||||
delete(attrs, "default")
|
||||
defaultValue, moreDiags := def.Expr.Value(ectx)
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
if res.Type != cty.NilType {
|
||||
var err error
|
||||
defaultValue, err = convert.Convert(defaultValue, res.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid default value for variable",
|
||||
Detail: fmt.Sprintf("This default value is not compatible with the variable's type constraint: %s.", err),
|
||||
Subject: def.Expr.Range().Ptr(),
|
||||
})
|
||||
defaultValue = cty.DynamicVal
|
||||
}
|
||||
}
|
||||
|
||||
res.DefaultValue = defaultValue
|
||||
}
|
||||
if len(attrs) > 0 {
|
||||
keys := []string{}
|
||||
for k := range attrs {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: "Unknown keys",
|
||||
Detail: fmt.Sprintf("unknown variable setting(s): %s", keys),
|
||||
Context: block.DefRange.Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
(*variables)[block.Labels[0]] = res
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
// Prefix your environment variables with VarEnvPrefix so that Packer can see
|
||||
// them.
|
||||
const VarEnvPrefix = "PKR_VAR_"
|
||||
|
||||
func (variables Variables) collectVariableValues(env []string, files []*hcl.File, argv map[string]string) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
for _, raw := range env {
|
||||
if !strings.HasPrefix(raw, VarEnvPrefix) {
|
||||
continue
|
||||
}
|
||||
raw = raw[len(VarEnvPrefix):] // trim the prefix
|
||||
|
||||
eq := strings.Index(raw, "=")
|
||||
if eq == -1 {
|
||||
// Seems invalid, so we'll ignore it.
|
||||
continue
|
||||
}
|
||||
|
||||
name := raw[:eq]
|
||||
value := raw[eq+1:]
|
||||
|
||||
variable, found := variables[name]
|
||||
if !found {
|
||||
// this variable was not defined in the hcl files, let's skip it !
|
||||
continue
|
||||
}
|
||||
|
||||
fakeFilename := fmt.Sprintf("<value for var.%s from env>", name)
|
||||
expr, moreDiags := hclsyntax.ParseExpression([]byte(value), fakeFilename, hcl.Pos{Line: 1, Column: 1})
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
val, valDiags := expr.Value(nil)
|
||||
diags = append(diags, valDiags...)
|
||||
|
||||
if variable.Type != cty.NilType {
|
||||
var err error
|
||||
val, err = convert.Convert(val, variable.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid value for variable",
|
||||
Detail: fmt.Sprintf("The value for %s is not compatible with the variable's type constraint: %s.", name, err),
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
val = cty.DynamicVal
|
||||
}
|
||||
}
|
||||
|
||||
variable.EnvValue = val
|
||||
}
|
||||
|
||||
// files will contain files found in the folder then files passed as
|
||||
// arguments.
|
||||
for _, file := range files {
|
||||
// Before we do our real decode, we'll probe to see if there are any
|
||||
// blocks of type "variable" in this body, since it's a common mistake
|
||||
// for new users to put variable declarations in pkrvars rather than
|
||||
// variable value definitions, and otherwise our error message for that
|
||||
// case is not so helpful.
|
||||
{
|
||||
content, _, _ := file.Body.PartialContent(&hcl.BodySchema{
|
||||
Blocks: []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "variable",
|
||||
LabelNames: []string{"name"},
|
||||
},
|
||||
},
|
||||
})
|
||||
for _, block := range content.Blocks {
|
||||
name := block.Labels[0]
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Variable declaration in a .pkrvar file",
|
||||
Detail: fmt.Sprintf("A .pkrvar file is used to assign "+
|
||||
"values to variables that have already been declared "+
|
||||
"in .pkr files, not to declare new variables. To "+
|
||||
"declare variable %q, place this block in one of your"+
|
||||
" .pkr files,such as variables.pkr.hcl\n\nTo set a "+
|
||||
"value for this variable in %s, use the definition "+
|
||||
"syntax instead:\n %s = <value>",
|
||||
name, block.TypeRange.Filename, name),
|
||||
Subject: &block.TypeRange,
|
||||
})
|
||||
}
|
||||
if diags.HasErrors() {
|
||||
// If we already found problems then JustAttributes below will find
|
||||
// the same problems with less-helpful messages, so we'll bail for
|
||||
// now to let the user focus on the immediate problem.
|
||||
return diags
|
||||
}
|
||||
}
|
||||
|
||||
attrs, moreDiags := file.Body.JustAttributes()
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
for name, attr := range attrs {
|
||||
variable, found := variables[name]
|
||||
if !found {
|
||||
// No file defines this variable; let's skip it
|
||||
continue
|
||||
}
|
||||
|
||||
val, moreDiags := attr.Expr.Value(nil)
|
||||
diags = append(diags, moreDiags...)
|
||||
|
||||
if variable.Type != cty.NilType {
|
||||
var err error
|
||||
val, err = convert.Convert(val, variable.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid value for variable",
|
||||
Detail: fmt.Sprintf("The value for %s is not compatible with the variable's type constraint: %s.", name, err),
|
||||
Subject: attr.Expr.Range().Ptr(),
|
||||
})
|
||||
val = cty.DynamicVal
|
||||
}
|
||||
}
|
||||
|
||||
variable.VarfileValue = val
|
||||
}
|
||||
}
|
||||
|
||||
// Finally we process values given explicitly on the command line.
|
||||
for name, value := range argv {
|
||||
variable, found := variables[name]
|
||||
if !found {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: "Unknown -var variable",
|
||||
Detail: fmt.Sprintf("A %q variable was passed in the command "+
|
||||
"line but was not found in known variables."+
|
||||
"To declare variable %q, place this block in one of your"+
|
||||
" .pkr files,such as variables.pkr.hcl",
|
||||
name, name),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
fakeFilename := fmt.Sprintf("<value for var.%s from arguments>", name)
|
||||
expr, moreDiags := hclsyntax.ParseExpression([]byte(value), fakeFilename, hcl.Pos{Line: 1, Column: 1})
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
val, valDiags := expr.Value(nil)
|
||||
diags = append(diags, valDiags...)
|
||||
|
||||
if variable.Type != cty.NilType {
|
||||
var err error
|
||||
val, err = convert.Convert(val, variable.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid argument value for -var variable",
|
||||
Detail: fmt.Sprintf("The received arg value for %s is not compatible with the variable's type constraint: %s.", name, err),
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
val = cty.DynamicVal
|
||||
}
|
||||
}
|
||||
|
||||
variable.CmdValue = val
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
package hcl2template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
|
@ -12,18 +19,331 @@ func TestParse_variables(t *testing.T) {
|
|||
tests := []parseTest{
|
||||
{"basic variables",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/variables/basic.pkr.hcl"},
|
||||
parseTestArgs{"testdata/variables/basic.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Variables: PackerV1Variables{
|
||||
"image_name": "foo-image-{{user `my_secret`}}",
|
||||
"key": "value",
|
||||
"my_secret": "foo",
|
||||
Basedir: filepath.Join("testdata", "variables"),
|
||||
InputVariables: Variables{
|
||||
"image_name": &Variable{},
|
||||
"key": &Variable{},
|
||||
"my_secret": &Variable{},
|
||||
"image_id": &Variable{},
|
||||
"port": &Variable{},
|
||||
"availability_zone_names": &Variable{
|
||||
Description: fmt.Sprintln("Describing is awesome ;D"),
|
||||
},
|
||||
"super_secret_password": &Variable{
|
||||
Sensitive: true,
|
||||
Description: fmt.Sprintln("Handle with care plz"),
|
||||
},
|
||||
},
|
||||
LocalVariables: Variables{
|
||||
"owner": &Variable{},
|
||||
"service_name": &Variable{},
|
||||
},
|
||||
},
|
||||
false, false,
|
||||
[]packer.Build{},
|
||||
false,
|
||||
},
|
||||
{"duplicate variable",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/variables/duplicate_variable.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "variables"),
|
||||
InputVariables: Variables{
|
||||
"boolean_value": &Variable{},
|
||||
},
|
||||
},
|
||||
true, true,
|
||||
[]packer.Build{},
|
||||
false,
|
||||
},
|
||||
{"duplicate variable in variables",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/variables/duplicate_variables.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "variables"),
|
||||
InputVariables: Variables{
|
||||
"boolean_value": &Variable{},
|
||||
},
|
||||
},
|
||||
true, true,
|
||||
[]packer.Build{},
|
||||
false,
|
||||
},
|
||||
{"invalid default type",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/variables/invalid_default.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "variables"),
|
||||
InputVariables: Variables{
|
||||
"broken_type": &Variable{},
|
||||
},
|
||||
},
|
||||
true, true,
|
||||
[]packer.Build{},
|
||||
false,
|
||||
},
|
||||
{"invalid default type",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/variables/unknown_key.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "variables"),
|
||||
InputVariables: Variables{
|
||||
"broken_type": &Variable{},
|
||||
},
|
||||
},
|
||||
true, false,
|
||||
[]packer.Build{},
|
||||
false,
|
||||
},
|
||||
}
|
||||
testParse(t, tests)
|
||||
}
|
||||
|
||||
func TestVariables_collectVariableValues(t *testing.T) {
|
||||
type args struct {
|
||||
env []string
|
||||
hclFiles []string
|
||||
argv map[string]string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
variables Variables
|
||||
args args
|
||||
wantDiags bool
|
||||
wantVariables Variables
|
||||
wantValues map[string]cty.Value
|
||||
}{
|
||||
|
||||
{name: "string",
|
||||
variables: Variables{"used_string": &Variable{DefaultValue: cty.StringVal("default_value")}},
|
||||
args: args{
|
||||
env: []string{`PKR_VAR_used_string="env_value"`},
|
||||
hclFiles: []string{
|
||||
`used_string="xy"`,
|
||||
`used_string="varfile_value"`,
|
||||
},
|
||||
argv: map[string]string{
|
||||
"used_string": `"cmd_value"`,
|
||||
},
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: false,
|
||||
wantVariables: Variables{
|
||||
"used_string": &Variable{
|
||||
CmdValue: cty.StringVal("cmd_value"),
|
||||
VarfileValue: cty.StringVal("varfile_value"),
|
||||
EnvValue: cty.StringVal("env_value"),
|
||||
DefaultValue: cty.StringVal("default_value"),
|
||||
},
|
||||
},
|
||||
wantValues: map[string]cty.Value{
|
||||
"used_string": cty.StringVal("cmd_value"),
|
||||
},
|
||||
},
|
||||
|
||||
{name: "array of strings",
|
||||
variables: Variables{"used_strings": &Variable{
|
||||
DefaultValue: stringListVal("default_value_1"),
|
||||
Type: cty.List(cty.String),
|
||||
}},
|
||||
args: args{
|
||||
env: []string{`PKR_VAR_used_strings=["env_value_1", "env_value_2"]`},
|
||||
hclFiles: []string{
|
||||
`used_strings=["xy"]`,
|
||||
`used_strings=["varfile_value_1"]`,
|
||||
},
|
||||
argv: map[string]string{
|
||||
"used_strings": `["cmd_value_1"]`,
|
||||
},
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: false,
|
||||
wantVariables: Variables{
|
||||
"used_strings": &Variable{
|
||||
Type: cty.List(cty.String),
|
||||
CmdValue: stringListVal("cmd_value_1"),
|
||||
VarfileValue: stringListVal("varfile_value_1"),
|
||||
EnvValue: stringListVal("env_value_1", "env_value_2"),
|
||||
DefaultValue: stringListVal("default_value_1"),
|
||||
},
|
||||
},
|
||||
wantValues: map[string]cty.Value{
|
||||
"used_strings": stringListVal("cmd_value_1"),
|
||||
},
|
||||
},
|
||||
|
||||
{name: "invalid env var",
|
||||
variables: Variables{"used_string": &Variable{DefaultValue: cty.StringVal("default_value")}},
|
||||
args: args{
|
||||
env: []string{`PKR_VAR_used_string`},
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: false,
|
||||
wantVariables: Variables{
|
||||
"used_string": &Variable{
|
||||
DefaultValue: cty.StringVal("default_value"),
|
||||
},
|
||||
},
|
||||
wantValues: map[string]cty.Value{
|
||||
"used_string": cty.StringVal("default_value"),
|
||||
},
|
||||
},
|
||||
|
||||
{name: "undefined but set value",
|
||||
variables: Variables{},
|
||||
args: args{
|
||||
env: []string{`PKR_VAR_unused_string=value`},
|
||||
hclFiles: []string{`unused_string="value"`},
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: false,
|
||||
wantVariables: Variables{},
|
||||
wantValues: map[string]cty.Value{},
|
||||
},
|
||||
|
||||
{name: "undefined but set value - args",
|
||||
variables: Variables{},
|
||||
args: args{
|
||||
argv: map[string]string{
|
||||
"unused_string": "value",
|
||||
},
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: true,
|
||||
wantVariables: Variables{},
|
||||
wantValues: map[string]cty.Value{},
|
||||
},
|
||||
|
||||
{name: "value not corresponding to type - env",
|
||||
variables: Variables{
|
||||
"used_string": &Variable{
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
env: []string{`PKR_VAR_used_string=["string"]`},
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: true,
|
||||
wantVariables: Variables{
|
||||
"used_string": &Variable{
|
||||
Type: cty.String,
|
||||
EnvValue: cty.DynamicVal,
|
||||
},
|
||||
},
|
||||
wantValues: map[string]cty.Value{
|
||||
"used_string": cty.DynamicVal,
|
||||
},
|
||||
},
|
||||
|
||||
{name: "value not corresponding to type - cfg file",
|
||||
variables: Variables{
|
||||
"used_string": &Variable{
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
hclFiles: []string{`used_string=["string"]`},
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: true,
|
||||
wantVariables: Variables{
|
||||
"used_string": &Variable{
|
||||
Type: cty.String,
|
||||
VarfileValue: cty.DynamicVal,
|
||||
},
|
||||
},
|
||||
wantValues: map[string]cty.Value{
|
||||
"used_string": cty.DynamicVal,
|
||||
},
|
||||
},
|
||||
|
||||
{name: "value not corresponding to type - argv",
|
||||
variables: Variables{
|
||||
"used_string": &Variable{
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
argv: map[string]string{
|
||||
"used_string": `["string"]`,
|
||||
},
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: true,
|
||||
wantVariables: Variables{
|
||||
"used_string": &Variable{
|
||||
Type: cty.String,
|
||||
CmdValue: cty.DynamicVal,
|
||||
},
|
||||
},
|
||||
wantValues: map[string]cty.Value{
|
||||
"used_string": cty.DynamicVal,
|
||||
},
|
||||
},
|
||||
|
||||
{name: "defining a variable block in a variables file is invalid ",
|
||||
variables: Variables{},
|
||||
args: args{
|
||||
hclFiles: []string{`variable "something" {}`},
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: true,
|
||||
wantVariables: Variables{},
|
||||
wantValues: map[string]cty.Value{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var files []*hcl.File
|
||||
parser := getBasicParser()
|
||||
for i, hclContent := range tt.args.hclFiles {
|
||||
file, diags := parser.ParseHCL([]byte(hclContent), fmt.Sprintf("test_file_%d_*"+hcl2VarFileExt, i))
|
||||
if diags != nil {
|
||||
t.Fatalf("ParseHCLFile %d: %v", i, diags)
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
if gotDiags := tt.variables.collectVariableValues(tt.args.env, files, tt.args.argv); (gotDiags == nil) == tt.wantDiags {
|
||||
t.Fatalf("Variables.collectVariableValues() = %v, want %v", gotDiags, tt.wantDiags)
|
||||
}
|
||||
if diff := cmp.Diff(fmt.Sprintf("%#v", tt.wantVariables), fmt.Sprintf("%#v", tt.variables)); diff != "" {
|
||||
t.Fatalf("didn't get expected variables: %s", diff)
|
||||
}
|
||||
values := map[string]cty.Value{}
|
||||
for k, v := range tt.variables {
|
||||
value, diag := v.Value()
|
||||
if diag != nil {
|
||||
t.Fatalf("Value %s: %v", k, diag)
|
||||
}
|
||||
values[k] = value
|
||||
}
|
||||
if diff := cmp.Diff(fmt.Sprintf("%#v", values), fmt.Sprintf("%#v", tt.wantValues)); diff != "" {
|
||||
t.Fatalf("didn't get expected values: %s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func stringListVal(strings ...string) cty.Value {
|
||||
values := []cty.Value{}
|
||||
for _, str := range strings {
|
||||
values = append(values, cty.StringVal(str))
|
||||
}
|
||||
list, err := convert.Convert(cty.ListVal(values), cty.List(cty.String))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
|
|
@ -37,7 +37,14 @@ func isDir(name string) (bool, error) {
|
|||
return s.IsDir(), nil
|
||||
}
|
||||
|
||||
func GetHCL2Files(filename string) (hclFiles, jsonFiles []string, diags hcl.Diagnostics) {
|
||||
// GetHCL2Files returns two slices of json formatted and hcl formatted files,
|
||||
// hclSuffix and jsonSuffix tell which file is what. Filename can be a folder
|
||||
// or a file.
|
||||
//
|
||||
// When filename is a folder all files of folder matching the suffixes will be
|
||||
// returned. Otherwise if filename references a file and filename matches one
|
||||
// of the suffixes it is returned in the according slice.
|
||||
func GetHCL2Files(filename, hclSuffix, jsonSuffix string) (hclFiles, jsonFiles []string, diags hcl.Diagnostics) {
|
||||
isDir, err := isDir(filename)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
|
@ -48,12 +55,13 @@ func GetHCL2Files(filename string) (hclFiles, jsonFiles []string, diags hcl.Diag
|
|||
return nil, nil, diags
|
||||
}
|
||||
if !isDir {
|
||||
if strings.HasSuffix(filename, hcl2JsonFileExt) {
|
||||
if strings.HasSuffix(filename, jsonSuffix) {
|
||||
return nil, []string{filename}, diags
|
||||
}
|
||||
if strings.HasSuffix(filename, hcl2FileExt) {
|
||||
if strings.HasSuffix(filename, hclSuffix) {
|
||||
return []string{filename}, nil, diags
|
||||
}
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
fileInfos, err := ioutil.ReadDir(filename)
|
||||
|
@ -71,20 +79,12 @@ func GetHCL2Files(filename string) (hclFiles, jsonFiles []string, diags hcl.Diag
|
|||
continue
|
||||
}
|
||||
filename := filepath.Join(filename, fileInfo.Name())
|
||||
if strings.HasSuffix(filename, hcl2FileExt) {
|
||||
if strings.HasSuffix(filename, hclSuffix) {
|
||||
hclFiles = append(hclFiles, filename)
|
||||
} else if strings.HasSuffix(filename, hcl2JsonFileExt) {
|
||||
} else if strings.HasSuffix(filename, jsonSuffix) {
|
||||
jsonFiles = append(jsonFiles, filename)
|
||||
}
|
||||
}
|
||||
if len(hclFiles)+len(jsonFiles) == 0 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Could not find any config file in " + filename,
|
||||
Detail: "A config file must be suffixed with `.pkr.hcl` or " +
|
||||
"`.pkr.json`. A folder can be referenced.",
|
||||
})
|
||||
}
|
||||
|
||||
return hclFiles, jsonFiles, diags
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2015 Martin Atkins
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -0,0 +1,218 @@
|
|||
// Package cidr is a collection of assorted utilities for computing
|
||||
// network and host addresses within network ranges.
|
||||
//
|
||||
// It expects a CIDR-type address structure where addresses are divided into
|
||||
// some number of prefix bits representing the network and then the remaining
|
||||
// suffix bits represent the host.
|
||||
//
|
||||
// For example, it can help to calculate addresses for sub-networks of a
|
||||
// parent network, or to calculate host addresses within a particular prefix.
|
||||
//
|
||||
// At present this package is prioritizing simplicity of implementation and
|
||||
// de-prioritizing speed and memory usage. Thus caution is advised before
|
||||
// using this package in performance-critical applications or hot codepaths.
|
||||
// Patches to improve the speed and memory usage may be accepted as long as
|
||||
// they do not result in a significant increase in code complexity.
|
||||
package cidr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Subnet takes a parent CIDR range and creates a subnet within it
|
||||
// with the given number of additional prefix bits and the given
|
||||
// network number.
|
||||
//
|
||||
// For example, 10.3.0.0/16, extended by 8 bits, with a network number
|
||||
// of 5, becomes 10.3.5.0/24 .
|
||||
func Subnet(base *net.IPNet, newBits int, num int) (*net.IPNet, error) {
|
||||
ip := base.IP
|
||||
mask := base.Mask
|
||||
|
||||
parentLen, addrLen := mask.Size()
|
||||
newPrefixLen := parentLen + newBits
|
||||
|
||||
if newPrefixLen > addrLen {
|
||||
return nil, fmt.Errorf("insufficient address space to extend prefix of %d by %d", parentLen, newBits)
|
||||
}
|
||||
|
||||
maxNetNum := uint64(1<<uint64(newBits)) - 1
|
||||
if uint64(num) > maxNetNum {
|
||||
return nil, fmt.Errorf("prefix extension of %d does not accommodate a subnet numbered %d", newBits, num)
|
||||
}
|
||||
|
||||
return &net.IPNet{
|
||||
IP: insertNumIntoIP(ip, big.NewInt(int64(num)), newPrefixLen),
|
||||
Mask: net.CIDRMask(newPrefixLen, addrLen),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Host takes a parent CIDR range and turns it into a host IP address with
|
||||
// the given host number.
|
||||
//
|
||||
// For example, 10.3.0.0/16 with a host number of 2 gives 10.3.0.2.
|
||||
func Host(base *net.IPNet, num int) (net.IP, error) {
|
||||
ip := base.IP
|
||||
mask := base.Mask
|
||||
bigNum := big.NewInt(int64(num))
|
||||
|
||||
parentLen, addrLen := mask.Size()
|
||||
hostLen := addrLen - parentLen
|
||||
|
||||
maxHostNum := big.NewInt(int64(1))
|
||||
maxHostNum.Lsh(maxHostNum, uint(hostLen))
|
||||
maxHostNum.Sub(maxHostNum, big.NewInt(1))
|
||||
|
||||
numUint64 := big.NewInt(int64(bigNum.Uint64()))
|
||||
if bigNum.Cmp(big.NewInt(0)) == -1 {
|
||||
numUint64.Neg(bigNum)
|
||||
numUint64.Sub(numUint64, big.NewInt(int64(1)))
|
||||
bigNum.Sub(maxHostNum, numUint64)
|
||||
}
|
||||
|
||||
if numUint64.Cmp(maxHostNum) == 1 {
|
||||
return nil, fmt.Errorf("prefix of %d does not accommodate a host numbered %d", parentLen, num)
|
||||
}
|
||||
var bitlength int
|
||||
if ip.To4() != nil {
|
||||
bitlength = 32
|
||||
} else {
|
||||
bitlength = 128
|
||||
}
|
||||
return insertNumIntoIP(ip, bigNum, bitlength), nil
|
||||
}
|
||||
|
||||
// AddressRange returns the first and last addresses in the given CIDR range.
|
||||
func AddressRange(network *net.IPNet) (net.IP, net.IP) {
|
||||
// the first IP is easy
|
||||
firstIP := network.IP
|
||||
|
||||
// the last IP is the network address OR NOT the mask address
|
||||
prefixLen, bits := network.Mask.Size()
|
||||
if prefixLen == bits {
|
||||
// Easy!
|
||||
// But make sure that our two slices are distinct, since they
|
||||
// would be in all other cases.
|
||||
lastIP := make([]byte, len(firstIP))
|
||||
copy(lastIP, firstIP)
|
||||
return firstIP, lastIP
|
||||
}
|
||||
|
||||
firstIPInt, bits := ipToInt(firstIP)
|
||||
hostLen := uint(bits) - uint(prefixLen)
|
||||
lastIPInt := big.NewInt(1)
|
||||
lastIPInt.Lsh(lastIPInt, hostLen)
|
||||
lastIPInt.Sub(lastIPInt, big.NewInt(1))
|
||||
lastIPInt.Or(lastIPInt, firstIPInt)
|
||||
|
||||
return firstIP, intToIP(lastIPInt, bits)
|
||||
}
|
||||
|
||||
// AddressCount returns the number of distinct host addresses within the given
|
||||
// CIDR range.
|
||||
//
|
||||
// Since the result is a uint64, this function returns meaningful information
|
||||
// only for IPv4 ranges and IPv6 ranges with a prefix size of at least 65.
|
||||
func AddressCount(network *net.IPNet) uint64 {
|
||||
prefixLen, bits := network.Mask.Size()
|
||||
return 1 << (uint64(bits) - uint64(prefixLen))
|
||||
}
|
||||
|
||||
//VerifyNoOverlap takes a list subnets and supernet (CIDRBlock) and verifies
|
||||
//none of the subnets overlap and all subnets are in the supernet
|
||||
//it returns an error if any of those conditions are not satisfied
|
||||
func VerifyNoOverlap(subnets []*net.IPNet, CIDRBlock *net.IPNet) error {
|
||||
firstLastIP := make([][]net.IP, len(subnets))
|
||||
for i, s := range subnets {
|
||||
first, last := AddressRange(s)
|
||||
firstLastIP[i] = []net.IP{first, last}
|
||||
}
|
||||
for i, s := range subnets {
|
||||
if !CIDRBlock.Contains(firstLastIP[i][0]) || !CIDRBlock.Contains(firstLastIP[i][1]) {
|
||||
return fmt.Errorf("%s does not fully contain %s", CIDRBlock.String(), s.String())
|
||||
}
|
||||
for j := 0; j < len(subnets); j++ {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
|
||||
first := firstLastIP[j][0]
|
||||
last := firstLastIP[j][1]
|
||||
if s.Contains(first) || s.Contains(last) {
|
||||
return fmt.Errorf("%s overlaps with %s", subnets[j].String(), s.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PreviousSubnet returns the subnet of the desired mask in the IP space
|
||||
// just lower than the start of IPNet provided. If the IP space rolls over
|
||||
// then the second return value is true
|
||||
func PreviousSubnet(network *net.IPNet, prefixLen int) (*net.IPNet, bool) {
|
||||
startIP := checkIPv4(network.IP)
|
||||
previousIP := make(net.IP, len(startIP))
|
||||
copy(previousIP, startIP)
|
||||
cMask := net.CIDRMask(prefixLen, 8*len(previousIP))
|
||||
previousIP = Dec(previousIP)
|
||||
previous := &net.IPNet{IP: previousIP.Mask(cMask), Mask: cMask}
|
||||
if startIP.Equal(net.IPv4zero) || startIP.Equal(net.IPv6zero) {
|
||||
return previous, true
|
||||
}
|
||||
return previous, false
|
||||
}
|
||||
|
||||
// NextSubnet returns the next available subnet of the desired mask size
|
||||
// starting for the maximum IP of the offset subnet
|
||||
// If the IP exceeds the maxium IP then the second return value is true
|
||||
func NextSubnet(network *net.IPNet, prefixLen int) (*net.IPNet, bool) {
|
||||
_, currentLast := AddressRange(network)
|
||||
mask := net.CIDRMask(prefixLen, 8*len(currentLast))
|
||||
currentSubnet := &net.IPNet{IP: currentLast.Mask(mask), Mask: mask}
|
||||
_, last := AddressRange(currentSubnet)
|
||||
last = Inc(last)
|
||||
next := &net.IPNet{IP: last.Mask(mask), Mask: mask}
|
||||
if last.Equal(net.IPv4zero) || last.Equal(net.IPv6zero) {
|
||||
return next, true
|
||||
}
|
||||
return next, false
|
||||
}
|
||||
|
||||
//Inc increases the IP by one this returns a new []byte for the IP
|
||||
func Inc(IP net.IP) net.IP {
|
||||
IP = checkIPv4(IP)
|
||||
incIP := make([]byte, len(IP))
|
||||
copy(incIP, IP)
|
||||
for j := len(incIP) - 1; j >= 0; j-- {
|
||||
incIP[j]++
|
||||
if incIP[j] > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return incIP
|
||||
}
|
||||
|
||||
//Dec decreases the IP by one this returns a new []byte for the IP
|
||||
func Dec(IP net.IP) net.IP {
|
||||
IP = checkIPv4(IP)
|
||||
decIP := make([]byte, len(IP))
|
||||
copy(decIP, IP)
|
||||
decIP = checkIPv4(decIP)
|
||||
for j := len(decIP) - 1; j >= 0; j-- {
|
||||
decIP[j]--
|
||||
if decIP[j] < 255 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return decIP
|
||||
}
|
||||
|
||||
func checkIPv4(ip net.IP) net.IP {
|
||||
// Go for some reason allocs IPv6len for IPv4 so we have to correct it
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
return v4
|
||||
}
|
||||
return ip
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package cidr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
)
|
||||
|
||||
func ipToInt(ip net.IP) (*big.Int, int) {
|
||||
val := &big.Int{}
|
||||
val.SetBytes([]byte(ip))
|
||||
if len(ip) == net.IPv4len {
|
||||
return val, 32
|
||||
} else if len(ip) == net.IPv6len {
|
||||
return val, 128
|
||||
} else {
|
||||
panic(fmt.Errorf("Unsupported address length %d", len(ip)))
|
||||
}
|
||||
}
|
||||
|
||||
func intToIP(ipInt *big.Int, bits int) net.IP {
|
||||
ipBytes := ipInt.Bytes()
|
||||
ret := make([]byte, bits/8)
|
||||
// Pack our IP bytes into the end of the return array,
|
||||
// since big.Int.Bytes() removes front zero padding.
|
||||
for i := 1; i <= len(ipBytes); i++ {
|
||||
ret[len(ret)-i] = ipBytes[len(ipBytes)-i]
|
||||
}
|
||||
return net.IP(ret)
|
||||
}
|
||||
|
||||
func insertNumIntoIP(ip net.IP, bigNum *big.Int, prefixLen int) net.IP {
|
||||
ipInt, totalBits := ipToInt(ip)
|
||||
bigNum.Lsh(bigNum, uint(totalBits-prefixLen))
|
||||
ipInt.Or(ipInt, bigNum)
|
||||
return intToIP(ipInt, totalBits)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
# vi
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# test directory
|
||||
test/
|
|
@ -0,0 +1,15 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.11
|
||||
- 1.12
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Bob Matcuk
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
![Release](https://img.shields.io/github/release/bmatcuk/doublestar.svg?branch=master)
|
||||
[![Build Status](https://travis-ci.org/bmatcuk/doublestar.svg?branch=master)](https://travis-ci.org/bmatcuk/doublestar)
|
||||
[![codecov.io](https://img.shields.io/codecov/c/github/bmatcuk/doublestar.svg?branch=master)](https://codecov.io/github/bmatcuk/doublestar?branch=master)
|
||||
|
||||
# doublestar
|
||||
|
||||
**doublestar** is a [golang](http://golang.org/) implementation of path pattern
|
||||
matching and globbing with support for "doublestar" (aka globstar: `**`)
|
||||
patterns.
|
||||
|
||||
doublestar patterns match files and directories recursively. For example, if
|
||||
you had the following directory structure:
|
||||
|
||||
```
|
||||
grandparent
|
||||
`-- parent
|
||||
|-- child1
|
||||
`-- child2
|
||||
```
|
||||
|
||||
You could find the children with patterns such as: `**/child*`,
|
||||
`grandparent/**/child?`, `**/parent/*`, or even just `**` by itself (which will
|
||||
return all files and directories recursively).
|
||||
|
||||
Bash's globstar is doublestar's inspiration and, as such, works similarly.
|
||||
Note that the doublestar must appear as a path component by itself. A pattern
|
||||
such as `/path**` is invalid and will be treated the same as `/path*`, but
|
||||
`/path*/**` should achieve the desired result. Additionally, `/path/**` will
|
||||
match all directories and files under the path directory, but `/path/**/` will
|
||||
only match directories.
|
||||
|
||||
## Installation
|
||||
|
||||
**doublestar** can be installed via `go get`:
|
||||
|
||||
```bash
|
||||
go get github.com/bmatcuk/doublestar
|
||||
```
|
||||
|
||||
To use it in your code, you must import it:
|
||||
|
||||
```go
|
||||
import "github.com/bmatcuk/doublestar"
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### Match
|
||||
```go
|
||||
func Match(pattern, name string) (bool, error)
|
||||
```
|
||||
|
||||
Match returns true if `name` matches the file name `pattern`
|
||||
([see below](#patterns)). `name` and `pattern` are split on forward slash (`/`)
|
||||
characters and may be relative or absolute.
|
||||
|
||||
Note: `Match()` is meant to be a drop-in replacement for `path.Match()`. As
|
||||
such, it always uses `/` as the path separator. If you are writing code that
|
||||
will run on systems where `/` is not the path separator (such as Windows), you
|
||||
want to use `PathMatch()` (below) instead.
|
||||
|
||||
|
||||
### PathMatch
|
||||
```go
|
||||
func PathMatch(pattern, name string) (bool, error)
|
||||
```
|
||||
|
||||
PathMatch returns true if `name` matches the file name `pattern`
|
||||
([see below](#patterns)). The difference between Match and PathMatch is that
|
||||
PathMatch will automatically use your system's path separator to split `name`
|
||||
and `pattern`.
|
||||
|
||||
`PathMatch()` is meant to be a drop-in replacement for `filepath.Match()`.
|
||||
|
||||
### Glob
|
||||
```go
|
||||
func Glob(pattern string) ([]string, error)
|
||||
```
|
||||
|
||||
Glob finds all files and directories in the filesystem that match `pattern`
|
||||
([see below](#patterns)). `pattern` may be relative (to the current working
|
||||
directory), or absolute.
|
||||
|
||||
`Glob()` is meant to be a drop-in replacement for `filepath.Glob()`.
|
||||
|
||||
## Patterns
|
||||
|
||||
**doublestar** supports the following special terms in the patterns:
|
||||
|
||||
Special Terms | Meaning
|
||||
------------- | -------
|
||||
`*` | matches any sequence of non-path-separators
|
||||
`**` | matches any sequence of characters, including path separators
|
||||
`?` | matches any single non-path-separator character
|
||||
`[class]` | matches any single non-path-separator character against a class of characters ([see below](#character-classes))
|
||||
`{alt1,...}` | matches a sequence of characters if one of the comma-separated alternatives matches
|
||||
|
||||
Any character with a special meaning can be escaped with a backslash (`\`).
|
||||
|
||||
### Character Classes
|
||||
|
||||
Character classes support the following:
|
||||
|
||||
Class | Meaning
|
||||
---------- | -------
|
||||
`[abc]` | matches any single character within the set
|
||||
`[a-z]` | matches any single character in the range
|
||||
`[^class]` | matches any single character which does *not* match the class
|
||||
|
|
@ -0,0 +1,476 @@
|
|||
package doublestar
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ErrBadPattern indicates a pattern was malformed.
|
||||
var ErrBadPattern = path.ErrBadPattern
|
||||
|
||||
// Split a path on the given separator, respecting escaping.
|
||||
func splitPathOnSeparator(path string, separator rune) (ret []string) {
|
||||
idx := 0
|
||||
if separator == '\\' {
|
||||
// if the separator is '\\', then we can just split...
|
||||
ret = strings.Split(path, string(separator))
|
||||
idx = len(ret)
|
||||
} else {
|
||||
// otherwise, we need to be careful of situations where the separator was escaped
|
||||
cnt := strings.Count(path, string(separator))
|
||||
if cnt == 0 {
|
||||
return []string{path}
|
||||
}
|
||||
|
||||
ret = make([]string, cnt+1)
|
||||
pathlen := len(path)
|
||||
separatorLen := utf8.RuneLen(separator)
|
||||
emptyEnd := false
|
||||
for start := 0; start < pathlen; {
|
||||
end := indexRuneWithEscaping(path[start:], separator)
|
||||
if end == -1 {
|
||||
emptyEnd = false
|
||||
end = pathlen
|
||||
} else {
|
||||
emptyEnd = true
|
||||
end += start
|
||||
}
|
||||
ret[idx] = path[start:end]
|
||||
start = end + separatorLen
|
||||
idx++
|
||||
}
|
||||
|
||||
// If the last rune is a path separator, we need to append an empty string to
|
||||
// represent the last, empty path component. By default, the strings from
|
||||
// make([]string, ...) will be empty, so we just need to icrement the count
|
||||
if emptyEnd {
|
||||
idx++
|
||||
}
|
||||
}
|
||||
|
||||
return ret[:idx]
|
||||
}
|
||||
|
||||
// Find the first index of a rune in a string,
|
||||
// ignoring any times the rune is escaped using "\".
|
||||
func indexRuneWithEscaping(s string, r rune) int {
|
||||
end := strings.IndexRune(s, r)
|
||||
if end == -1 {
|
||||
return -1
|
||||
}
|
||||
if end > 0 && s[end-1] == '\\' {
|
||||
start := end + utf8.RuneLen(r)
|
||||
end = indexRuneWithEscaping(s[start:], r)
|
||||
if end != -1 {
|
||||
end += start
|
||||
}
|
||||
}
|
||||
return end
|
||||
}
|
||||
|
||||
// Match returns true if name matches the shell file name pattern.
|
||||
// The pattern syntax is:
|
||||
//
|
||||
// pattern:
|
||||
// { term }
|
||||
// term:
|
||||
// '*' matches any sequence of non-path-separators
|
||||
// '**' matches any sequence of characters, including
|
||||
// path separators.
|
||||
// '?' matches any single non-path-separator character
|
||||
// '[' [ '^' ] { character-range } ']'
|
||||
// character class (must be non-empty)
|
||||
// '{' { term } [ ',' { term } ... ] '}'
|
||||
// c matches character c (c != '*', '?', '\\', '[')
|
||||
// '\\' c matches character c
|
||||
//
|
||||
// character-range:
|
||||
// c matches character c (c != '\\', '-', ']')
|
||||
// '\\' c matches character c
|
||||
// lo '-' hi matches character c for lo <= c <= hi
|
||||
//
|
||||
// Match requires pattern to match all of name, not just a substring.
|
||||
// The path-separator defaults to the '/' character. The only possible
|
||||
// returned error is ErrBadPattern, when pattern is malformed.
|
||||
//
|
||||
// Note: this is meant as a drop-in replacement for path.Match() which
|
||||
// always uses '/' as the path separator. If you want to support systems
|
||||
// which use a different path separator (such as Windows), what you want
|
||||
// is the PathMatch() function below.
|
||||
//
|
||||
func Match(pattern, name string) (bool, error) {
|
||||
return matchWithSeparator(pattern, name, '/')
|
||||
}
|
||||
|
||||
// PathMatch is like Match except that it uses your system's path separator.
|
||||
// For most systems, this will be '/'. However, for Windows, it would be '\\'.
|
||||
// Note that for systems where the path separator is '\\', escaping is
|
||||
// disabled.
|
||||
//
|
||||
// Note: this is meant as a drop-in replacement for filepath.Match().
|
||||
//
|
||||
func PathMatch(pattern, name string) (bool, error) {
|
||||
return matchWithSeparator(pattern, name, os.PathSeparator)
|
||||
}
|
||||
|
||||
// Match returns true if name matches the shell file name pattern.
|
||||
// The pattern syntax is:
|
||||
//
|
||||
// pattern:
|
||||
// { term }
|
||||
// term:
|
||||
// '*' matches any sequence of non-path-separators
|
||||
// '**' matches any sequence of characters, including
|
||||
// path separators.
|
||||
// '?' matches any single non-path-separator character
|
||||
// '[' [ '^' ] { character-range } ']'
|
||||
// character class (must be non-empty)
|
||||
// '{' { term } [ ',' { term } ... ] '}'
|
||||
// c matches character c (c != '*', '?', '\\', '[')
|
||||
// '\\' c matches character c
|
||||
//
|
||||
// character-range:
|
||||
// c matches character c (c != '\\', '-', ']')
|
||||
// '\\' c matches character c, unless separator is '\\'
|
||||
// lo '-' hi matches character c for lo <= c <= hi
|
||||
//
|
||||
// Match requires pattern to match all of name, not just a substring.
|
||||
// The only possible returned error is ErrBadPattern, when pattern
|
||||
// is malformed.
|
||||
//
|
||||
func matchWithSeparator(pattern, name string, separator rune) (bool, error) {
|
||||
patternComponents := splitPathOnSeparator(pattern, separator)
|
||||
nameComponents := splitPathOnSeparator(name, separator)
|
||||
return doMatching(patternComponents, nameComponents)
|
||||
}
|
||||
|
||||
func doMatching(patternComponents, nameComponents []string) (matched bool, err error) {
|
||||
// check for some base-cases
|
||||
patternLen, nameLen := len(patternComponents), len(nameComponents)
|
||||
if patternLen == 0 && nameLen == 0 {
|
||||
return true, nil
|
||||
}
|
||||
if patternLen == 0 || nameLen == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
patIdx, nameIdx := 0, 0
|
||||
for patIdx < patternLen && nameIdx < nameLen {
|
||||
if patternComponents[patIdx] == "**" {
|
||||
// if our last pattern component is a doublestar, we're done -
|
||||
// doublestar will match any remaining name components, if any.
|
||||
if patIdx++; patIdx >= patternLen {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// otherwise, try matching remaining components
|
||||
for ; nameIdx < nameLen; nameIdx++ {
|
||||
if m, _ := doMatching(patternComponents[patIdx:], nameComponents[nameIdx:]); m {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// try matching components
|
||||
matched, err = matchComponent(patternComponents[patIdx], nameComponents[nameIdx])
|
||||
if !matched || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
patIdx++
|
||||
nameIdx++
|
||||
}
|
||||
return patIdx >= patternLen && nameIdx >= nameLen, nil
|
||||
}
|
||||
|
||||
// Glob returns the names of all files matching pattern or nil
|
||||
// if there is no matching file. The syntax of pattern is the same
|
||||
// as in Match. The pattern may describe hierarchical names such as
|
||||
// /usr/*/bin/ed (assuming the Separator is '/').
|
||||
//
|
||||
// Glob ignores file system errors such as I/O errors reading directories.
|
||||
// The only possible returned error is ErrBadPattern, when pattern
|
||||
// is malformed.
|
||||
//
|
||||
// Your system path separator is automatically used. This means on
|
||||
// systems where the separator is '\\' (Windows), escaping will be
|
||||
// disabled.
|
||||
//
|
||||
// Note: this is meant as a drop-in replacement for filepath.Glob().
|
||||
//
|
||||
func Glob(pattern string) (matches []string, err error) {
|
||||
patternComponents := splitPathOnSeparator(filepath.ToSlash(pattern), '/')
|
||||
if len(patternComponents) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// On Windows systems, this will return the drive name ('C:') for filesystem
|
||||
// paths, or \\<server>\<share> for UNC paths. On other systems, it will
|
||||
// return an empty string. Since absolute paths on non-Windows systems start
|
||||
// with a slash, patternComponent[0] == volumeName will return true for both
|
||||
// absolute Windows paths and absolute non-Windows paths, but we need a
|
||||
// separate check for UNC paths.
|
||||
volumeName := filepath.VolumeName(pattern)
|
||||
isWindowsUNC := strings.HasPrefix(pattern, `\\`)
|
||||
if isWindowsUNC || patternComponents[0] == volumeName {
|
||||
startComponentIndex := 1
|
||||
if isWindowsUNC {
|
||||
startComponentIndex = 4
|
||||
}
|
||||
return doGlob(fmt.Sprintf("%s%s", volumeName, string(os.PathSeparator)), patternComponents[startComponentIndex:], matches)
|
||||
}
|
||||
|
||||
// otherwise, it's a relative pattern
|
||||
return doGlob(".", patternComponents, matches)
|
||||
}
|
||||
|
||||
// Perform a glob
|
||||
func doGlob(basedir string, components, matches []string) (m []string, e error) {
|
||||
m = matches
|
||||
e = nil
|
||||
|
||||
// figure out how many components we don't need to glob because they're
|
||||
// just names without patterns - we'll use os.Lstat below to check if that
|
||||
// path actually exists
|
||||
patLen := len(components)
|
||||
patIdx := 0
|
||||
for ; patIdx < patLen; patIdx++ {
|
||||
if strings.IndexAny(components[patIdx], "*?[{\\") >= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if patIdx > 0 {
|
||||
basedir = filepath.Join(basedir, filepath.Join(components[0:patIdx]...))
|
||||
}
|
||||
|
||||
// Lstat will return an error if the file/directory doesn't exist
|
||||
fi, err := os.Lstat(basedir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if there are no more components, we've found a match
|
||||
if patIdx >= patLen {
|
||||
m = append(m, basedir)
|
||||
return
|
||||
}
|
||||
|
||||
// otherwise, we need to check each item in the directory...
|
||||
// first, if basedir is a symlink, follow it...
|
||||
if (fi.Mode() & os.ModeSymlink) != 0 {
|
||||
fi, err = os.Stat(basedir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// confirm it's a directory...
|
||||
if !fi.IsDir() {
|
||||
return
|
||||
}
|
||||
|
||||
// read directory
|
||||
dir, err := os.Open(basedir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
files, _ := dir.Readdir(-1)
|
||||
lastComponent := (patIdx + 1) >= patLen
|
||||
if components[patIdx] == "**" {
|
||||
// if the current component is a doublestar, we'll try depth-first
|
||||
for _, file := range files {
|
||||
// if symlink, we may want to follow
|
||||
if (file.Mode() & os.ModeSymlink) != 0 {
|
||||
file, err = os.Stat(filepath.Join(basedir, file.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if file.IsDir() {
|
||||
// recurse into directories
|
||||
if lastComponent {
|
||||
m = append(m, filepath.Join(basedir, file.Name()))
|
||||
}
|
||||
m, e = doGlob(filepath.Join(basedir, file.Name()), components[patIdx:], m)
|
||||
} else if lastComponent {
|
||||
// if the pattern's last component is a doublestar, we match filenames, too
|
||||
m = append(m, filepath.Join(basedir, file.Name()))
|
||||
}
|
||||
}
|
||||
if lastComponent {
|
||||
return // we're done
|
||||
}
|
||||
patIdx++
|
||||
lastComponent = (patIdx + 1) >= patLen
|
||||
}
|
||||
|
||||
// check items in current directory and recurse
|
||||
var match bool
|
||||
for _, file := range files {
|
||||
match, e = matchComponent(components[patIdx], file.Name())
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
if match {
|
||||
if lastComponent {
|
||||
m = append(m, filepath.Join(basedir, file.Name()))
|
||||
} else {
|
||||
m, e = doGlob(filepath.Join(basedir, file.Name()), components[patIdx+1:], m)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Attempt to match a single pattern component with a path component
|
||||
func matchComponent(pattern, name string) (bool, error) {
|
||||
// check some base cases
|
||||
patternLen, nameLen := len(pattern), len(name)
|
||||
if patternLen == 0 && nameLen == 0 {
|
||||
return true, nil
|
||||
}
|
||||
if patternLen == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if nameLen == 0 && pattern != "*" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// check for matches one rune at a time
|
||||
patIdx, nameIdx := 0, 0
|
||||
for patIdx < patternLen && nameIdx < nameLen {
|
||||
patRune, patAdj := utf8.DecodeRuneInString(pattern[patIdx:])
|
||||
nameRune, nameAdj := utf8.DecodeRuneInString(name[nameIdx:])
|
||||
if patRune == '\\' {
|
||||
// handle escaped runes
|
||||
patIdx += patAdj
|
||||
patRune, patAdj = utf8.DecodeRuneInString(pattern[patIdx:])
|
||||
if patRune == utf8.RuneError {
|
||||
return false, ErrBadPattern
|
||||
} else if patRune == nameRune {
|
||||
patIdx += patAdj
|
||||
nameIdx += nameAdj
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
} else if patRune == '*' {
|
||||
// handle stars
|
||||
if patIdx += patAdj; patIdx >= patternLen {
|
||||
// a star at the end of a pattern will always
|
||||
// match the rest of the path
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// check if we can make any matches
|
||||
for ; nameIdx < nameLen; nameIdx += nameAdj {
|
||||
if m, _ := matchComponent(pattern[patIdx:], name[nameIdx:]); m {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
} else if patRune == '[' {
|
||||
// handle character sets
|
||||
patIdx += patAdj
|
||||
endClass := indexRuneWithEscaping(pattern[patIdx:], ']')
|
||||
if endClass == -1 {
|
||||
return false, ErrBadPattern
|
||||
}
|
||||
endClass += patIdx
|
||||
classRunes := []rune(pattern[patIdx:endClass])
|
||||
classRunesLen := len(classRunes)
|
||||
if classRunesLen > 0 {
|
||||
classIdx := 0
|
||||
matchClass := false
|
||||
if classRunes[0] == '^' {
|
||||
classIdx++
|
||||
}
|
||||
for classIdx < classRunesLen {
|
||||
low := classRunes[classIdx]
|
||||
if low == '-' {
|
||||
return false, ErrBadPattern
|
||||
}
|
||||
classIdx++
|
||||
if low == '\\' {
|
||||
if classIdx < classRunesLen {
|
||||
low = classRunes[classIdx]
|
||||
classIdx++
|
||||
} else {
|
||||
return false, ErrBadPattern
|
||||
}
|
||||
}
|
||||
high := low
|
||||
if classIdx < classRunesLen && classRunes[classIdx] == '-' {
|
||||
// we have a range of runes
|
||||
if classIdx++; classIdx >= classRunesLen {
|
||||
return false, ErrBadPattern
|
||||
}
|
||||
high = classRunes[classIdx]
|
||||
if high == '-' {
|
||||
return false, ErrBadPattern
|
||||
}
|
||||
classIdx++
|
||||
if high == '\\' {
|
||||
if classIdx < classRunesLen {
|
||||
high = classRunes[classIdx]
|
||||
classIdx++
|
||||
} else {
|
||||
return false, ErrBadPattern
|
||||
}
|
||||
}
|
||||
}
|
||||
if low <= nameRune && nameRune <= high {
|
||||
matchClass = true
|
||||
}
|
||||
}
|
||||
if matchClass == (classRunes[0] == '^') {
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
return false, ErrBadPattern
|
||||
}
|
||||
patIdx = endClass + 1
|
||||
nameIdx += nameAdj
|
||||
} else if patRune == '{' {
|
||||
// handle alternatives such as {alt1,alt2,...}
|
||||
patIdx += patAdj
|
||||
endOptions := indexRuneWithEscaping(pattern[patIdx:], '}')
|
||||
if endOptions == -1 {
|
||||
return false, ErrBadPattern
|
||||
}
|
||||
endOptions += patIdx
|
||||
options := splitPathOnSeparator(pattern[patIdx:endOptions], ',')
|
||||
patIdx = endOptions + 1
|
||||
for _, o := range options {
|
||||
m, e := matchComponent(o+pattern[patIdx:], name[nameIdx:])
|
||||
if e != nil {
|
||||
return false, e
|
||||
}
|
||||
if m {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
} else if patRune == '?' || patRune == nameRune {
|
||||
// handle single-rune wildcard
|
||||
patIdx += patAdj
|
||||
nameIdx += nameAdj
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if patIdx >= patternLen && nameIdx >= nameLen {
|
||||
return true, nil
|
||||
}
|
||||
if nameIdx >= nameLen && pattern[patIdx:] == "*" || pattern[patIdx:] == "**" {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/bmatcuk/doublestar
|
||||
|
||||
go 1.12
|
|
@ -0,0 +1 @@
|
|||
module github.com/google/uuid
|
|
@ -48,6 +48,7 @@ func setNodeInterface(name string) bool {
|
|||
// does not specify a specific interface generate a random Node ID
|
||||
// (section 4.1.6)
|
||||
if name == "" {
|
||||
ifname = "random"
|
||||
randomBits(nodeID[:])
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
|
@ -35,20 +35,43 @@ const (
|
|||
|
||||
var rander = rand.Reader // random function
|
||||
|
||||
// Parse decodes s into a UUID or returns an error. Both the UUID form of
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
|
||||
// Parse decodes s into a UUID or returns an error. Both the standard UUID
|
||||
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
|
||||
// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex
|
||||
// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
|
||||
func Parse(s string) (UUID, error) {
|
||||
var uuid UUID
|
||||
if len(s) != 36 {
|
||||
if len(s) != 36+9 {
|
||||
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
|
||||
}
|
||||
switch len(s) {
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36:
|
||||
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9:
|
||||
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
case 36 + 2:
|
||||
s = s[1:]
|
||||
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
case 32:
|
||||
var ok bool
|
||||
for i := range uuid {
|
||||
uuid[i], ok = xtob(s[i*2], s[i*2+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
|
@ -70,15 +93,29 @@ func Parse(s string) (UUID, error) {
|
|||
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
||||
func ParseBytes(b []byte) (UUID, error) {
|
||||
var uuid UUID
|
||||
if len(b) != 36 {
|
||||
if len(b) != 36+9 {
|
||||
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
|
||||
}
|
||||
switch len(b) {
|
||||
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
|
||||
}
|
||||
b = b[9:]
|
||||
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
b = b[1:]
|
||||
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
var ok bool
|
||||
for i := 0; i < 32; i += 2 {
|
||||
uuid[i/2], ok = xtob(b[i], b[i+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
|
@ -97,6 +134,16 @@ func ParseBytes(b []byte) (UUID, error) {
|
|||
return uuid, nil
|
||||
}
|
||||
|
||||
// MustParse is like Parse but panics if the string cannot be parsed.
|
||||
// It simplifies safe initialization of global variables holding compiled UUIDs.
|
||||
func MustParse(s string) UUID {
|
||||
uuid, err := Parse(s)
|
||||
if err != nil {
|
||||
panic(`uuid: Parse(` + s + `): ` + err.Error())
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
|
||||
// does not have a length of 16. The bytes are copied from the slice.
|
||||
func FromBytes(b []byte) (uuid UUID, err error) {
|
||||
|
@ -130,7 +177,7 @@ func (uuid UUID) URN() string {
|
|||
}
|
||||
|
||||
func encodeHex(dst []byte, uuid UUID) {
|
||||
hex.Encode(dst[:], uuid[:4])
|
||||
hex.Encode(dst, uuid[:4])
|
||||
dst[8] = '-'
|
||||
hex.Encode(dst[9:13], uuid[4:6])
|
||||
dst[13] = '-'
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/hashicorp/go-cty-funcs/cidr
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/apparentlymart/go-cidr v1.0.1
|
||||
github.com/zclconf/go-cty v1.2.1
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
github.com/apparentlymart/go-cidr v1.0.1 h1:NmIwLZ/KdsjIUlhf+/Np40atNXm/+lZ5txfTJ/SpF+U=
|
||||
github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
||||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8=
|
||||
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
@ -0,0 +1,49 @@
|
|||
package cidr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/apparentlymart/go-cidr/cidr"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
)
|
||||
|
||||
// HostFunc is a function that calculates a full host IP address within a given
|
||||
// IP network address prefix.
|
||||
var HostFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "prefix",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "hostnum",
|
||||
Type: cty.Number,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
var hostNum int
|
||||
if err := gocty.FromCtyValue(args[1], &hostNum); err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
_, network, err := net.ParseCIDR(args[0].AsString())
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
|
||||
}
|
||||
|
||||
ip, err := cidr.Host(network, hostNum)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
|
||||
return cty.StringVal(ip.String()), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Host calculates a full host IP address within a given IP network address prefix.
|
||||
func Host(prefix, hostnum cty.Value) (cty.Value, error) {
|
||||
return HostFunc.Call([]cty.Value{prefix, hostnum})
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package cidr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// NetmaskFunc is a function that converts an IPv4 address prefix given in CIDR
|
||||
// notation into a subnet mask address.
|
||||
var NetmaskFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "prefix",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
_, network, err := net.ParseCIDR(args[0].AsString())
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
|
||||
}
|
||||
|
||||
return cty.StringVal(net.IP(network.Mask).String()), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Netmask converts an IPv4 address prefix given in CIDR notation into a subnet mask address.
|
||||
func Netmask(prefix cty.Value) (cty.Value, error) {
|
||||
return NetmaskFunc.Call([]cty.Value{prefix})
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package cidr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/apparentlymart/go-cidr/cidr"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
)
|
||||
|
||||
// SubnetFunc is a function that calculates a subnet address within a given
|
||||
// IP network address prefix.
|
||||
var SubnetFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "prefix",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "newbits",
|
||||
Type: cty.Number,
|
||||
},
|
||||
{
|
||||
Name: "netnum",
|
||||
Type: cty.Number,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
var newbits int
|
||||
if err := gocty.FromCtyValue(args[1], &newbits); err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
var netnum int
|
||||
if err := gocty.FromCtyValue(args[2], &netnum); err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
|
||||
_, network, err := net.ParseCIDR(args[0].AsString())
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("invalid CIDR expression: %s", err)
|
||||
}
|
||||
|
||||
// For portability with 32-bit systems where the subnet number will be
|
||||
// a 32-bit int, we only allow extension of 32 bits in one call even if
|
||||
// we're running on a 64-bit machine. (Of course, this is significant
|
||||
// only for IPv6.)
|
||||
if newbits > 32 {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("may not extend prefix by more than 32 bits")
|
||||
}
|
||||
|
||||
newNetwork, err := cidr.Subnet(network, newbits, netnum)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
|
||||
return cty.StringVal(newNetwork.String()), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Subnet calculates a subnet address within a given IP network address prefix.
|
||||
func Subnet(prefix, newbits, netnum cty.Value) (cty.Value, error) {
|
||||
return SubnetFunc.Call([]cty.Value{prefix, newbits, netnum})
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package cidr
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/apparentlymart/go-cidr/cidr"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
)
|
||||
|
||||
// SubnetsFunc is similar to SubnetFunc but calculates many consecutive subnet
|
||||
// addresses at once, rather than just a single subnet extension.
|
||||
var SubnetsFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "prefix",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
VarParam: &function.Parameter{
|
||||
Name: "newbits",
|
||||
Type: cty.Number,
|
||||
},
|
||||
Type: function.StaticReturnType(cty.List(cty.String)),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
_, network, err := net.ParseCIDR(args[0].AsString())
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid CIDR expression: %s", err)
|
||||
}
|
||||
startPrefixLen, _ := network.Mask.Size()
|
||||
|
||||
prefixLengthArgs := args[1:]
|
||||
if len(prefixLengthArgs) == 0 {
|
||||
return cty.ListValEmpty(cty.String), nil
|
||||
}
|
||||
|
||||
var firstLength int
|
||||
if err := gocty.FromCtyValue(prefixLengthArgs[0], &firstLength); err != nil {
|
||||
return cty.UnknownVal(cty.String), function.NewArgError(1, err)
|
||||
}
|
||||
firstLength += startPrefixLen
|
||||
|
||||
retVals := make([]cty.Value, len(prefixLengthArgs))
|
||||
|
||||
current, _ := cidr.PreviousSubnet(network, firstLength)
|
||||
for i, lengthArg := range prefixLengthArgs {
|
||||
var length int
|
||||
if err := gocty.FromCtyValue(lengthArg, &length); err != nil {
|
||||
return cty.UnknownVal(cty.String), function.NewArgError(i+1, err)
|
||||
}
|
||||
|
||||
if length < 1 {
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "must extend prefix by at least one bit")
|
||||
}
|
||||
// For portability with 32-bit systems where the subnet number
|
||||
// will be a 32-bit int, we only allow extension of 32 bits in
|
||||
// one call even if we're running on a 64-bit machine.
|
||||
// (Of course, this is significant only for IPv6.)
|
||||
if length > 32 {
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "may not extend prefix by more than 32 bits")
|
||||
}
|
||||
length += startPrefixLen
|
||||
if length > (len(network.IP) * 8) {
|
||||
protocol := "IP"
|
||||
switch len(network.IP) * 8 {
|
||||
case 32:
|
||||
protocol = "IPv4"
|
||||
case 128:
|
||||
protocol = "IPv6"
|
||||
}
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "would extend prefix to %d bits, which is too long for an %s address", length, protocol)
|
||||
}
|
||||
|
||||
next, rollover := cidr.NextSubnet(current, length)
|
||||
if rollover || !network.Contains(next.IP) {
|
||||
// If we run out of suffix bits in the base CIDR prefix then
|
||||
// NextSubnet will start incrementing the prefix bits, which
|
||||
// we don't allow because it would then allocate addresses
|
||||
// outside of the caller's given prefix.
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(i+1, "not enough remaining address space for a subnet with a prefix of %d bits after %s", length, current.String())
|
||||
}
|
||||
|
||||
current = next
|
||||
retVals[i] = cty.StringVal(current.String())
|
||||
}
|
||||
|
||||
return cty.ListVal(retVals), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Subnets calculates a sequence of consecutive subnet prefixes that may be of
|
||||
// different prefix lengths under a common base prefix.
|
||||
func Subnets(prefix cty.Value, newbits ...cty.Value) (cty.Value, error) {
|
||||
args := make([]cty.Value, len(newbits)+1)
|
||||
args[0] = prefix
|
||||
copy(args[1:], newbits)
|
||||
return SubnetsFunc.Call(args)
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// BcryptFunc is a function that computes a hash of the given string using the
|
||||
// Blowfish cipher.
|
||||
var BcryptFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "str",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
VarParam: &function.Parameter{
|
||||
Name: "cost",
|
||||
Type: cty.Number,
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
defaultCost := 10
|
||||
|
||||
if len(args) > 1 {
|
||||
var val int
|
||||
if err := gocty.FromCtyValue(args[1], &val); err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
defaultCost = val
|
||||
}
|
||||
|
||||
if len(args) > 2 {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("bcrypt() takes no more than two arguments")
|
||||
}
|
||||
|
||||
input := args[0].AsString()
|
||||
out, err := bcrypt.GenerateFromPassword([]byte(input), defaultCost)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("error occured generating password %s", err.Error())
|
||||
}
|
||||
|
||||
return cty.StringVal(string(out)), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Bcrypt computes a hash of the given string using the Blowfish cipher,
|
||||
// returning a string in the Modular Crypt Format usually expected in the
|
||||
// shadow password file on many Unix systems.
|
||||
func Bcrypt(str cty.Value, cost ...cty.Value) (cty.Value, error) {
|
||||
args := make([]cty.Value, len(cost)+1)
|
||||
args[0] = str
|
||||
copy(args[1:], cost)
|
||||
return BcryptFunc.Call(args)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
module github.com/hashicorp/go-cty-funcs/crypto
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/hashicorp/go-uuid v1.0.2
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/zclconf/go-cty v1.2.1
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
)
|
|
@ -0,0 +1,32 @@
|
|||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8=
|
||||
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg=
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
@ -0,0 +1,249 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// Base64Sha256Func is a function that computes the SHA256 hash of a given
|
||||
// string and encodes it with Base64.
|
||||
var Base64Sha256Func = makeStringHashFunction(sha256.New, base64.StdEncoding.EncodeToString)
|
||||
|
||||
// MakeFileBase64Sha256Func is a function that is like Base64Sha256Func but
|
||||
// reads the contents of a file rather than hashing a given literal string.
|
||||
func MakeFileBase64Sha256Func(baseDir string) function.Function {
|
||||
return makeFileHashFunction(baseDir, sha256.New, base64.StdEncoding.EncodeToString)
|
||||
}
|
||||
|
||||
// Base64Sha512Func is a function that computes the SHA256 hash of a given
|
||||
// string and encodes it with Base64.
|
||||
var Base64Sha512Func = makeStringHashFunction(sha512.New, base64.StdEncoding.EncodeToString)
|
||||
|
||||
// MakeFileBase64Sha512Func is a function that is like Base64Sha512Func but
|
||||
// reads the contents of a file rather than hashing a given literal string.
|
||||
func MakeFileBase64Sha512Func(baseDir string) function.Function {
|
||||
return makeFileHashFunction(baseDir, sha512.New, base64.StdEncoding.EncodeToString)
|
||||
}
|
||||
|
||||
// Md5Func is a function that computes the MD5 hash of a given string and
|
||||
// encodes it with hexadecimal digits.
|
||||
var Md5Func = makeStringHashFunction(md5.New, hex.EncodeToString)
|
||||
|
||||
// MakeFileMd5Func is a function that is like Md5Func but reads the contents of
|
||||
// a file rather than hashing a given literal string.
|
||||
func MakeFileMd5Func(baseDir string) function.Function {
|
||||
return makeFileHashFunction(baseDir, md5.New, hex.EncodeToString)
|
||||
}
|
||||
|
||||
// RsaDecryptFunc is a function that decrypts an RSA-encrypted ciphertext.
|
||||
var RsaDecryptFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "ciphertext",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "privatekey",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
s := args[0].AsString()
|
||||
key := args[1].AsString()
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode input %q: cipher text must be base64-encoded", s)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode([]byte(key))
|
||||
if block == nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("failed to parse key: no key found")
|
||||
}
|
||||
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf(
|
||||
"failed to parse key: password protected keys are not supported. Please decrypt the key prior to use",
|
||||
)
|
||||
}
|
||||
|
||||
x509Key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
|
||||
out, err := rsa.DecryptPKCS1v15(nil, x509Key, b)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
|
||||
return cty.StringVal(string(out)), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Sha1Func is a function that computes the SHA1 hash of a given string and
|
||||
// encodes it with hexadecimal digits.
|
||||
var Sha1Func = makeStringHashFunction(sha1.New, hex.EncodeToString)
|
||||
|
||||
// MakeFileSha1Func is a function that is like Sha1Func but reads the contents
|
||||
// of a file rather than hashing a given literal string.
|
||||
func MakeFileSha1Func(baseDir string) function.Function {
|
||||
return makeFileHashFunction(baseDir, sha1.New, hex.EncodeToString)
|
||||
}
|
||||
|
||||
// Sha256Func is a function that computes the SHA256 hash of a given string and
|
||||
// encodes it with hexadecimal digits.
|
||||
var Sha256Func = makeStringHashFunction(sha256.New, hex.EncodeToString)
|
||||
|
||||
// MakeFileSha256Func is a function that is like Sha256Func but reads the
|
||||
// contents of a file rather than hashing a given literal string.
|
||||
func MakeFileSha256Func(baseDir string) function.Function {
|
||||
return makeFileHashFunction(baseDir, sha256.New, hex.EncodeToString)
|
||||
}
|
||||
|
||||
// Sha512Func is a function that computes the SHA512 hash of a given string and
|
||||
// encodes it with hexadecimal digits.
|
||||
var Sha512Func = makeStringHashFunction(sha512.New, hex.EncodeToString)
|
||||
|
||||
// MakeFileSha512Func is a function that is like Sha512Func but reads the
|
||||
// contents of a file rather than hashing a given literal string.
|
||||
func MakeFileSha512Func(baseDir string) function.Function {
|
||||
return makeFileHashFunction(baseDir, sha512.New, hex.EncodeToString)
|
||||
}
|
||||
|
||||
func makeStringHashFunction(hf func() hash.Hash, enc func([]byte) string) function.Function {
|
||||
return function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "str",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
s := args[0].AsString()
|
||||
h := hf()
|
||||
h.Write([]byte(s))
|
||||
rv := enc(h.Sum(nil))
|
||||
return cty.StringVal(rv), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func makeFileHashFunction(baseDir string, hf func() hash.Hash, enc func([]byte) string) function.Function {
|
||||
return function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
path := args[0].AsString()
|
||||
src, err := readFileBytes(baseDir, path)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
|
||||
h := hf()
|
||||
h.Write(src)
|
||||
rv := enc(h.Sum(nil))
|
||||
return cty.StringVal(rv), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Base64Sha256 computes the SHA256 hash of a given string and encodes it with
|
||||
// Base64.
|
||||
//
|
||||
// The given string is first encoded as UTF-8 and then the SHA256 algorithm is
|
||||
// applied as defined in RFC 4634. The raw hash is then encoded with Base64
|
||||
// before returning. Terraform uses the "standard" Base64 alphabet as defined
|
||||
// in RFC 4648 section 4.
|
||||
func Base64Sha256(str cty.Value) (cty.Value, error) {
|
||||
return Base64Sha256Func.Call([]cty.Value{str})
|
||||
}
|
||||
|
||||
// Base64Sha512 computes the SHA512 hash of a given string and encodes it with
|
||||
// Base64.
|
||||
//
|
||||
// The given string is first encoded as UTF-8 and then the SHA256 algorithm is
|
||||
// applied as defined in RFC 4634. The raw hash is then encoded with Base64
|
||||
// before returning. Terraform uses the "standard" Base64 alphabet as defined
|
||||
// in RFC 4648 section 4
|
||||
func Base64Sha512(str cty.Value) (cty.Value, error) {
|
||||
return Base64Sha512Func.Call([]cty.Value{str})
|
||||
}
|
||||
|
||||
// Md5 computes the MD5 hash of a given string and encodes it with hexadecimal
|
||||
// digits.
|
||||
func Md5(str cty.Value) (cty.Value, error) {
|
||||
return Md5Func.Call([]cty.Value{str})
|
||||
}
|
||||
|
||||
// RsaDecrypt decrypts an RSA-encrypted ciphertext, returning the corresponding
|
||||
// cleartext.
|
||||
func RsaDecrypt(ciphertext, privatekey cty.Value) (cty.Value, error) {
|
||||
return RsaDecryptFunc.Call([]cty.Value{ciphertext, privatekey})
|
||||
}
|
||||
|
||||
// Sha1 computes the SHA1 hash of a given string and encodes it with
|
||||
// hexadecimal digits.
|
||||
func Sha1(str cty.Value) (cty.Value, error) {
|
||||
return Sha1Func.Call([]cty.Value{str})
|
||||
}
|
||||
|
||||
// Sha256 computes the SHA256 hash of a given string and encodes it with
|
||||
// hexadecimal digits.
|
||||
func Sha256(str cty.Value) (cty.Value, error) {
|
||||
return Sha256Func.Call([]cty.Value{str})
|
||||
}
|
||||
|
||||
// Sha512 computes the SHA512 hash of a given string and encodes it with
|
||||
// hexadecimal digits.
|
||||
func Sha512(str cty.Value) (cty.Value, error) {
|
||||
return Sha512Func.Call([]cty.Value{str})
|
||||
}
|
||||
|
||||
func readFileBytes(baseDir, path string) ([]byte, error) {
|
||||
path, err := homedir.Expand(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to expand ~: %s", err)
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(baseDir, path)
|
||||
}
|
||||
|
||||
// Ensure that the path is canonical for the host OS
|
||||
path = filepath.Clean(path)
|
||||
|
||||
src, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
// ReadFile does not return Terraform-user-friendly error messages, so
|
||||
// we'll provide our own.
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("no file exists at %s", path)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read %s", path)
|
||||
}
|
||||
|
||||
return src, nil
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
var UUIDFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
result, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
return cty.StringVal(result), nil
|
||||
},
|
||||
})
|
||||
|
||||
// UUID generates and returns a Type-4 UUID in the standard hexadecimal string
|
||||
// format.
|
||||
//
|
||||
// This is not a pure function: it will generate a different result for each
|
||||
// call. It must therefore be registered as an impure function in the function
|
||||
// table in the "lang" package.
|
||||
func UUID() (cty.Value, error) {
|
||||
return UUIDFunc.Call(nil)
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
uuidv5 "github.com/google/uuid"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
var UUIDV5Func = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "namespace",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "name",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
var namespace uuidv5.UUID
|
||||
switch {
|
||||
case args[0].AsString() == "dns":
|
||||
namespace = uuidv5.NameSpaceDNS
|
||||
case args[0].AsString() == "url":
|
||||
namespace = uuidv5.NameSpaceURL
|
||||
case args[0].AsString() == "oid":
|
||||
namespace = uuidv5.NameSpaceOID
|
||||
case args[0].AsString() == "x500":
|
||||
namespace = uuidv5.NameSpaceX500
|
||||
default:
|
||||
if namespace, err = uuidv5.Parse(args[0].AsString()); err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("uuidv5() doesn't support namespace %s (%v)", args[0].AsString(), err)
|
||||
}
|
||||
}
|
||||
val := args[1].AsString()
|
||||
return cty.StringVal(uuidv5.NewSHA1(namespace, []byte(val)).String()), nil
|
||||
},
|
||||
})
|
||||
|
||||
// UUIDV5 generates and returns a Type-5 UUID in the standard hexadecimal
|
||||
// string format.
|
||||
func UUIDV5(namespace cty.Value, name cty.Value) (cty.Value, error) {
|
||||
return UUIDV5Func.Call([]cty.Value{namespace, name})
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package encoding
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// Base64DecodeFunc is a function that decodes a string containing a base64 sequence.
|
||||
var Base64DecodeFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "str",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
s := args[0].AsString()
|
||||
sDec, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("failed to decode base64 data '%s'", s)
|
||||
}
|
||||
if !utf8.Valid([]byte(sDec)) {
|
||||
log.Printf("[DEBUG] the result of decoding the the provided string is not valid UTF-8: %s", sDec)
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("the result of decoding the the provided string is not valid UTF-8")
|
||||
}
|
||||
return cty.StringVal(string(sDec)), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Base64EncodeFunc is a function that encodes a string to a base64 sequence.
|
||||
var Base64EncodeFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "str",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return cty.StringVal(base64.StdEncoding.EncodeToString([]byte(args[0].AsString()))), nil
|
||||
},
|
||||
})
|
||||
|
||||
// Base64Decode decodes a string containing a base64 sequence.
|
||||
//
|
||||
// Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4.
|
||||
//
|
||||
// Strings in the Terraform language are sequences of unicode characters rather
|
||||
// than bytes, so this function will also interpret the resulting bytes as
|
||||
// UTF-8. If the bytes after Base64 decoding are _not_ valid UTF-8, this function
|
||||
// produces an error.
|
||||
func Base64Decode(str cty.Value) (cty.Value, error) {
|
||||
return Base64DecodeFunc.Call([]cty.Value{str})
|
||||
}
|
||||
|
||||
// Base64Encode applies Base64 encoding to a string.
|
||||
//
|
||||
// Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4.
|
||||
//
|
||||
// Strings in the Terraform language are sequences of unicode characters rather
|
||||
// than bytes, so this function will first encode the characters from the string
|
||||
// as UTF-8, and then apply Base64 encoding to the result.
|
||||
func Base64Encode(str cty.Value) (cty.Value, error) {
|
||||
return Base64EncodeFunc.Call([]cty.Value{str})
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
module github.com/hashicorp/go-cty-funcs/encoding
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/zclconf/go-cty v1.2.1
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
)
|
|
@ -0,0 +1,20 @@
|
|||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8=
|
||||
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
@ -0,0 +1,34 @@
|
|||
package encoding
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// URLEncodeFunc is a function that applies URL encoding to a given string.
|
||||
var URLEncodeFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "str",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return cty.StringVal(url.QueryEscape(args[0].AsString())), nil
|
||||
},
|
||||
})
|
||||
|
||||
// URLEncode applies URL encoding to a given string.
|
||||
//
|
||||
// This function identifies characters in the given string that would have a
|
||||
// special meaning when included as a query string argument in a URL and
|
||||
// escapes them using RFC 3986 "percent encoding".
|
||||
//
|
||||
// If the given string contains non-ASCII characters, these are first encoded as
|
||||
// UTF-8 and then percent encoding is applied separately to each UTF-8 byte.
|
||||
func URLEncode(str cty.Value) (cty.Value, error) {
|
||||
return URLEncodeFunc.Call([]cty.Value{str})
|
||||
}
|
312
vendor/github.com/hashicorp/go-cty-funcs/filesystem/filesystem.go
generated
vendored
Normal file
312
vendor/github.com/hashicorp/go-cty-funcs/filesystem/filesystem.go
generated
vendored
Normal file
|
@ -0,0 +1,312 @@
|
|||
package filesystem
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/bmatcuk/doublestar"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// MakeFileFunc constructs a function that takes a file path and returns the
|
||||
// contents of that file, either directly as a string (where valid UTF-8 is
|
||||
// required) or as a string containing base64 bytes.
|
||||
func MakeFileFunc(baseDir string, encBase64 bool) function.Function {
|
||||
return function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
path := args[0].AsString()
|
||||
src, err := readFileBytes(baseDir, path)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
|
||||
switch {
|
||||
case encBase64:
|
||||
enc := base64.StdEncoding.EncodeToString(src)
|
||||
return cty.StringVal(enc), nil
|
||||
default:
|
||||
if !utf8.Valid(src) {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("contents of %s are not valid UTF-8; use the filebase64 function to obtain the Base64 encoded contents or the other file functions (e.g. filemd5, filesha256) to obtain file hashing results instead", path)
|
||||
}
|
||||
return cty.StringVal(string(src)), nil
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// MakeFileExistsFunc is a function that takes a path and determines whether a
|
||||
// file exists at that path.
|
||||
//
|
||||
// MakeFileExistsFunc will try to expand a path starting with a '~' to the home
|
||||
// folder using github.com/mitchellh/go-homedir
|
||||
func MakeFileExistsFunc(baseDir string) function.Function {
|
||||
return function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
path := args[0].AsString()
|
||||
path, err := homedir.Expand(path)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to expand ~: %s", err)
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(baseDir, path)
|
||||
}
|
||||
|
||||
// Ensure that the path is canonical for the host OS
|
||||
path = filepath.Clean(path)
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return cty.False, nil
|
||||
}
|
||||
return cty.UnknownVal(cty.Bool), fmt.Errorf("failed to stat %s", path)
|
||||
}
|
||||
|
||||
if fi.Mode().IsRegular() {
|
||||
return cty.True, nil
|
||||
}
|
||||
|
||||
return cty.False, fmt.Errorf("%s is not a regular file, but %q",
|
||||
path, fi.Mode().String())
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// MakeFileSetFunc is a function that takes a glob pattern
|
||||
// and enumerates a file set from that pattern
|
||||
func MakeFileSetFunc(baseDir string) function.Function {
|
||||
return function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "pattern",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Set(cty.String)),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
path := args[0].AsString()
|
||||
pattern := args[1].AsString()
|
||||
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(baseDir, path)
|
||||
}
|
||||
|
||||
// Join the path to the glob pattern, while ensuring the full
|
||||
// pattern is canonical for the host OS. The joined path is
|
||||
// automatically cleaned during this operation.
|
||||
pattern = filepath.Join(path, pattern)
|
||||
|
||||
matches, err := doublestar.Glob(pattern)
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to glob pattern (%s): %s", pattern, err)
|
||||
}
|
||||
|
||||
var matchVals []cty.Value
|
||||
for _, match := range matches {
|
||||
fi, err := os.Stat(match)
|
||||
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to stat (%s): %s", match, err)
|
||||
}
|
||||
|
||||
if !fi.Mode().IsRegular() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Remove the path and file separator from matches.
|
||||
match, err = filepath.Rel(path, match)
|
||||
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.Set(cty.String)), fmt.Errorf("failed to trim path of match (%s): %s", match, err)
|
||||
}
|
||||
|
||||
// Replace any remaining file separators with forward slash (/)
|
||||
// separators for cross-system compatibility.
|
||||
match = filepath.ToSlash(match)
|
||||
|
||||
matchVals = append(matchVals, cty.StringVal(match))
|
||||
}
|
||||
|
||||
if len(matchVals) == 0 {
|
||||
return cty.SetValEmpty(cty.String), nil
|
||||
}
|
||||
|
||||
return cty.SetVal(matchVals), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// BasenameFunc is a function that takes a string containing a filesystem path
|
||||
// and removes all except the last portion from it.
|
||||
var BasenameFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return cty.StringVal(filepath.Base(args[0].AsString())), nil
|
||||
},
|
||||
})
|
||||
|
||||
// DirnameFunc is a function that takes a string containing a filesystem path
|
||||
// and removes the last portion from it.
|
||||
var DirnameFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return cty.StringVal(filepath.Dir(args[0].AsString())), nil
|
||||
},
|
||||
})
|
||||
|
||||
// AbsPathFunc is a function that converts a filesystem path to an absolute path
|
||||
var AbsPathFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
absPath, err := filepath.Abs(args[0].AsString())
|
||||
return cty.StringVal(filepath.ToSlash(absPath)), err
|
||||
},
|
||||
})
|
||||
|
||||
// PathExpandFunc is a function that expands a leading ~ character to the current user's home directory.
|
||||
var PathExpandFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "path",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
|
||||
homePath, err := homedir.Expand(args[0].AsString())
|
||||
return cty.StringVal(homePath), err
|
||||
},
|
||||
})
|
||||
|
||||
func readFileBytes(baseDir, path string) ([]byte, error) {
|
||||
path, err := homedir.Expand(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to expand ~: %s", err)
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(path) {
|
||||
path = filepath.Join(baseDir, path)
|
||||
}
|
||||
|
||||
// Ensure that the path is canonical for the host OS
|
||||
path = filepath.Clean(path)
|
||||
|
||||
src, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
// ReadFile does not return Terraform-user-friendly error
|
||||
// messages, so we'll provide our own.
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("no file exists at %s", path)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read %s", path)
|
||||
}
|
||||
|
||||
return src, nil
|
||||
}
|
||||
|
||||
// File reads the contents of the file at the given path.
|
||||
//
|
||||
// The file must contain valid UTF-8 bytes, or this function will return an error.
|
||||
//
|
||||
// The underlying function implementation works relative to a particular base
|
||||
// directory, so this wrapper takes a base directory string and uses it to
|
||||
// construct the underlying function before calling it.
|
||||
func File(baseDir string, path cty.Value) (cty.Value, error) {
|
||||
fn := MakeFileFunc(baseDir, false)
|
||||
return fn.Call([]cty.Value{path})
|
||||
}
|
||||
|
||||
// FileExists determines whether a file exists at the given path.
|
||||
//
|
||||
// The underlying function implementation works relative to a particular base
|
||||
// directory, so this wrapper takes a base directory string and uses it to
|
||||
// construct the underlying function before calling it.
|
||||
func FileExists(baseDir string, path cty.Value) (cty.Value, error) {
|
||||
fn := MakeFileExistsFunc(baseDir)
|
||||
return fn.Call([]cty.Value{path})
|
||||
}
|
||||
|
||||
// FileSet enumerates a set of files given a glob pattern
|
||||
//
|
||||
// The underlying function implementation works relative to a particular base
|
||||
// directory, so this wrapper takes a base directory string and uses it to
|
||||
// construct the underlying function before calling it.
|
||||
func FileSet(baseDir string, path, pattern cty.Value) (cty.Value, error) {
|
||||
fn := MakeFileSetFunc(baseDir)
|
||||
return fn.Call([]cty.Value{path, pattern})
|
||||
}
|
||||
|
||||
// Basename takes a string containing a filesystem path and removes all except the last portion from it.
|
||||
//
|
||||
// The underlying function implementation works only with the path string and does not access the filesystem itself.
|
||||
// It is therefore unable to take into account filesystem features such as symlinks.
|
||||
//
|
||||
// If the path is empty then the result is ".", representing the current working directory.
|
||||
func Basename(path cty.Value) (cty.Value, error) {
|
||||
return BasenameFunc.Call([]cty.Value{path})
|
||||
}
|
||||
|
||||
// Dirname takes a string containing a filesystem path and removes the last portion from it.
|
||||
//
|
||||
// The underlying function implementation works only with the path string and does not access the filesystem itself.
|
||||
// It is therefore unable to take into account filesystem features such as symlinks.
|
||||
//
|
||||
// If the path is empty then the result is ".", representing the current working directory.
|
||||
func Dirname(path cty.Value) (cty.Value, error) {
|
||||
return DirnameFunc.Call([]cty.Value{path})
|
||||
}
|
||||
|
||||
// Pathexpand takes a string that might begin with a `~` segment, and if so it replaces that segment with
|
||||
// the current user's home directory path.
|
||||
//
|
||||
// The underlying function implementation works only with the path string and does not access the filesystem itself.
|
||||
// It is therefore unable to take into account filesystem features such as symlinks.
|
||||
//
|
||||
// If the leading segment in the path is not `~` then the given path is returned unmodified.
|
||||
func Pathexpand(path cty.Value) (cty.Value, error) {
|
||||
return PathExpandFunc.Call([]cty.Value{path})
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/hashicorp/go-cty-funcs/filesystem
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/bmatcuk/doublestar v1.1.5
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/zclconf/go-cty v1.2.1
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||
github.com/bmatcuk/doublestar v1.1.5 h1:2bNwBOmhyFEFcoB3tGvTD5xanq+4kyOZlB8wFYbMjkk=
|
||||
github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8=
|
||||
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
@ -0,0 +1,9 @@
|
|||
module github.com/hashicorp/go-cty-funcs/uuid
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/hashicorp/go-uuid v1.0.2
|
||||
github.com/zclconf/go-cty v1.2.1
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8=
|
||||
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
@ -0,0 +1,28 @@
|
|||
package uuid
|
||||
|
||||
import (
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
var V4Func = function.New(&function.Spec{
|
||||
Params: []function.Parameter{},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
result, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
return cty.StringVal(result), nil
|
||||
},
|
||||
})
|
||||
|
||||
// V4 generates and returns a Type-4 UUID in the standard hexadecimal string
|
||||
// format.
|
||||
//
|
||||
// This is not a "pure" function: it will generate a different result for each
|
||||
// call.
|
||||
func V4() (cty.Value, error) {
|
||||
return V4Func.Call(nil)
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package uuid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
uuidv5 "github.com/google/uuid"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
var V5Func = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "namespace",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "name",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
var namespace uuidv5.UUID
|
||||
switch {
|
||||
case args[0].AsString() == "dns":
|
||||
namespace = uuidv5.NameSpaceDNS
|
||||
case args[0].AsString() == "url":
|
||||
namespace = uuidv5.NameSpaceURL
|
||||
case args[0].AsString() == "oid":
|
||||
namespace = uuidv5.NameSpaceOID
|
||||
case args[0].AsString() == "x500":
|
||||
namespace = uuidv5.NameSpaceX500
|
||||
default:
|
||||
if namespace, err = uuidv5.Parse(args[0].AsString()); err != nil {
|
||||
return cty.UnknownVal(cty.String), fmt.Errorf("uuidv5() doesn't support namespace %s (%v)", args[0].AsString(), err)
|
||||
}
|
||||
}
|
||||
val := args[1].AsString()
|
||||
return cty.StringVal(uuidv5.NewSHA1(namespace, []byte(val)).String()), nil
|
||||
},
|
||||
})
|
||||
|
||||
// V5 generates and returns a Type-5 UUID in the standard hexadecimal
|
||||
// string format.
|
||||
//
|
||||
// This is not a "pure" function: it will generate a different result for each
|
||||
// call.
|
||||
func V5(namespace cty.Value, name cty.Value) (cty.Value, error) {
|
||||
return V5Func.Call([]cty.Value{namespace, name})
|
||||
}
|
|
@ -4,22 +4,40 @@ import (
|
|||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// GenerateRandomBytes is used to generate random bytes of given size.
|
||||
func GenerateRandomBytes(size int) ([]byte, error) {
|
||||
return GenerateRandomBytesWithReader(size, rand.Reader)
|
||||
}
|
||||
|
||||
// GenerateRandomBytesWithReader is used to generate random bytes of given size read from a given reader.
|
||||
func GenerateRandomBytesWithReader(size int, reader io.Reader) ([]byte, error) {
|
||||
if reader == nil {
|
||||
return nil, fmt.Errorf("provided reader is nil")
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
if _, err := io.ReadFull(reader, buf); err != nil {
|
||||
return nil, fmt.Errorf("failed to read random bytes: %v", err)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
|
||||
const uuidLen = 16
|
||||
|
||||
// GenerateUUID is used to generate a random UUID
|
||||
func GenerateUUID() (string, error) {
|
||||
buf, err := GenerateRandomBytes(uuidLen)
|
||||
return GenerateUUIDWithReader(rand.Reader)
|
||||
}
|
||||
|
||||
// GenerateUUIDWithReader is used to generate a random UUID with a given Reader
|
||||
func GenerateUUIDWithReader(reader io.Reader) (string, error) {
|
||||
if reader == nil {
|
||||
return "", fmt.Errorf("provided reader is nil")
|
||||
}
|
||||
buf, err := GenerateRandomBytesWithReader(uuidLen, reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -1,5 +1,38 @@
|
|||
# HCL Changelog
|
||||
|
||||
## v2.3.0 (Jan 3, 2020)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* ext/tryfunc: Optional functions `try` and `can` to include in your `hcl.EvalContext` when evaluating expressions, which allow users to make decisions based on the success of expressions. ([#330](https://github.com/hashicorp/hcl/pull/330))
|
||||
* ext/typeexpr: Now has an optional function `convert` which you can include in your `hcl.EvalContext` when evaluating expressions, allowing users to convert values to specific type constraints using the type constraint expression syntax. ([#330](https://github.com/hashicorp/hcl/pull/330))
|
||||
* ext/typeexpr: A new `cty` capsule type `typeexpr.TypeConstraintType` which, when used as either a type constraint for a function parameter or as a type constraint for a `hcldec` attribute specification will cause the given expression to be interpreted as a type constraint expression rather than a value expression. ([#330](https://github.com/hashicorp/hcl/pull/330))
|
||||
* ext/customdecode: An optional extension that allows overriding the static decoding behavior for expressions either in function arguments or `hcldec` attribute specifications. ([#330](https://github.com/hashicorp/hcl/pull/330))
|
||||
* ext/customdecode: New `cty` capsuletypes `customdecode.ExpressionType` and `customdecode.ExpressionClosureType` which, when used as either a type constraint for a function parameter or as a type constraint for a `hcldec` attribute specification will cause the given expression (and, for the closure type, also the `hcl.EvalContext` it was evaluated in) to be captured for later analysis, rather than immediately evaluated. ([#330](https://github.com/hashicorp/hcl/pull/330))
|
||||
|
||||
## v2.2.0 (Dec 11, 2019)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* hcldec: Attribute evaluation (as part of `AttrSpec` or `BlockAttrsSpec`) now captures expression evaluation metadata in any errors it produces during type conversions, allowing for better feedback in calling applications that are able to make use of this metadata when printing diagnostic messages. ([#329](https://github.com/hashicorp/hcl/pull/329))
|
||||
|
||||
### Bugs Fixed
|
||||
|
||||
* hclsyntax: `IndexExpr`, `SplatExpr`, and `RelativeTraversalExpr` will now report a source range that covers all of their child expression nodes. Previously they would report only the operator part, such as `["foo"]`, `[*]`, or `.foo`, which was problematic for callers using source ranges for code analysis. ([#328](https://github.com/hashicorp/hcl/pull/328))
|
||||
* hclwrite: Parser will no longer panic when the input includes index, splat, or relative traversal syntax. ([#328](https://github.com/hashicorp/hcl/pull/328))
|
||||
|
||||
## v2.1.0 (Nov 19, 2019)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* gohcl: When decoding into a struct value with some fields already populated, those values will be retained if not explicitly overwritten in the given HCL body, with similar overriding/merging behavior as `json.Unmarshal` in the Go standard library.
|
||||
* hclwrite: New interface to set the expression for an attribute to be a raw token sequence, with no special processing. This has some caveats, so if you intend to use it please refer to the godoc comments. ([#320](https://github.com/hashicorp/hcl/pull/320))
|
||||
|
||||
### Bugs Fixed
|
||||
|
||||
* hclwrite: The `Body.Blocks` method was returing the blocks in an indefined order, rather than preserving the order of declaration in the source input. ([#313](https://github.com/hashicorp/hcl/pull/313))
|
||||
* hclwrite: The `TokensForTraversal` function (and thus in turn the `Body.SetAttributeTraversal` method) was not correctly handling index steps in traversals, and thus producing invalid results. ([#319](https://github.com/hashicorp/hcl/pull/319))
|
||||
|
||||
## v2.0.0 (Oct 2, 2019)
|
||||
|
||||
Initial release of HCL 2, which is a new implementating combining the HCL 1
|
||||
|
|
|
@ -8,7 +8,7 @@ towards devops tools, servers, etc.
|
|||
> **NOTE:** This is major version 2 of HCL, whose Go API is incompatible with
|
||||
> major version 1. Both versions are available for selection in Go Modules
|
||||
> projects. HCL 2 _cannot_ be imported from Go projects that are not using Go Modules. For more information, see
|
||||
> [our version selection guide](https://github.com/golang/go/wiki/Version-Selection).
|
||||
> [our version selection guide](https://github.com/hashicorp/hcl/wiki/Version-Selection).
|
||||
|
||||
HCL has both a _native syntax_, intended to be pleasant to read and write for
|
||||
humans, and a JSON-based variant that is easier for machines to generate
|
||||
|
@ -51,7 +51,8 @@ func main() {
|
|||
```
|
||||
|
||||
A lower-level API is available for applications that need more control over
|
||||
the parsing, decoding, and evaluation of configuration.
|
||||
the parsing, decoding, and evaluation of configuration. For more information,
|
||||
see [the package documentation](https://pkg.go.dev/github.com/hashicorp/hcl/v2).
|
||||
|
||||
## Why?
|
||||
|
||||
|
@ -156,9 +157,9 @@ syntax allows use of arbitrary expressions within JSON strings:
|
|||
|
||||
For more information, see the detailed specifications:
|
||||
|
||||
* [Syntax-agnostic Information Model](hcl/spec.md)
|
||||
* [HCL Native Syntax](hcl/hclsyntax/spec.md)
|
||||
* [JSON Representation](hcl/json/spec.md)
|
||||
* [Syntax-agnostic Information Model](spec.md)
|
||||
* [HCL Native Syntax](hclsyntax/spec.md)
|
||||
* [JSON Representation](json/spec.md)
|
||||
|
||||
## Changes in 2.0
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
build: off
|
||||
|
||||
clone_folder: c:\gopath\src\github.com\hashicorp\hcl
|
||||
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
GO111MODULE: on
|
||||
GOPROXY: https://goproxy.io
|
||||
|
||||
stack: go 1.12
|
||||
|
||||
test_script:
|
||||
- go test ./...
|
|
@ -0,0 +1,209 @@
|
|||
# HCL Custom Static Decoding Extension
|
||||
|
||||
This HCL extension provides a mechanism for defining arguments in an HCL-based
|
||||
language whose values are derived using custom decoding rules against the
|
||||
HCL expression syntax, overriding the usual behavior of normal expression
|
||||
evaluation.
|
||||
|
||||
"Arguments", for the purpose of this extension, currently includes the
|
||||
following two contexts:
|
||||
|
||||
* For applications using `hcldec` for dynamic decoding, a `hcldec.AttrSpec`
|
||||
or `hcldec.BlockAttrsSpec` can be given a special type constraint that
|
||||
opts in to custom decoding behavior for the attribute(s) that are selected
|
||||
by that specification.
|
||||
|
||||
* When working with the HCL native expression syntax, a function given in
|
||||
the `hcl.EvalContext` during evaluation can have parameters with special
|
||||
type constraints that opt in to custom decoding behavior for the argument
|
||||
expression associated with that parameter in any call.
|
||||
|
||||
The above use-cases are rather abstract, so we'll consider a motivating
|
||||
real-world example: sometimes we (language designers) need to allow users
|
||||
to specify type constraints directly in the language itself, such as in
|
||||
[Terraform's Input Variables](https://www.terraform.io/docs/configuration/variables.html).
|
||||
Terraform's `variable` blocks include an argument called `type` which takes
|
||||
a type constraint given using HCL expression building-blocks as defined by
|
||||
[the HCL `typeexpr` extension](../typeexpr/README.md).
|
||||
|
||||
A "type constraint expression" of that sort is not an expression intended to
|
||||
be evaluated in the usual way. Instead, the physical expression is
|
||||
deconstructed using [the static analysis operations](../../spec.md#static-analysis)
|
||||
to produce a `cty.Type` as the result, rather than a `cty.Value`.
|
||||
|
||||
The purpose of this Custom Static Decoding Extension, then, is to provide a
|
||||
bridge to allow that sort of custom decoding to be used via mechanisms that
|
||||
normally deal in `cty.Value`, such as `hcldec` and native syntax function
|
||||
calls as listed above.
|
||||
|
||||
(Note: [`gohcl`](https://pkg.go.dev/github.com/hashicorp/hcl/v2/gohcl) has
|
||||
its own mechanism to support this use case, exploiting the fact that it is
|
||||
working directly with "normal" Go types. Decoding into a struct field of
|
||||
type `hcl.Expression` obtains the expression directly without evaluating it
|
||||
first. The Custom Static Decoding Extension is not necessary for that `gohcl`
|
||||
technique. You can also implement custom decoding by working directly with
|
||||
the lowest-level HCL API, which separates extraction of and evaluation of
|
||||
expressions into two steps.)
|
||||
|
||||
## Custom Decoding Types
|
||||
|
||||
This extension relies on a convention implemented in terms of
|
||||
[_Capsule Types_ in the underlying `cty` type system](https://github.com/zclconf/go-cty/blob/master/docs/types.md#capsule-types). `cty` allows a capsule type to carry arbitrary
|
||||
extension metadata values as an aid to creating higher-level abstractions like
|
||||
this extension.
|
||||
|
||||
A custom argument decoding mode, then, is implemented by creating a new `cty`
|
||||
capsule type that implements the `ExtensionData` custom operation to return
|
||||
a decoding function when requested. For example:
|
||||
|
||||
```go
|
||||
var keywordType cty.Type
|
||||
keywordType = cty.CapsuleWithOps("keyword", reflect.TypeOf(""), &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case customdecode.CustomExpressionDecoder:
|
||||
return customdecode.CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
kw := hcl.ExprAsKeyword(expr)
|
||||
if kw == "" {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid keyword",
|
||||
Detail: "A keyword is required",
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
return cty.UnkownVal(keywordType), diags
|
||||
}
|
||||
return cty.CapsuleVal(keywordType, &kw)
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
The boilerplate here is a bit fussy, but the important part for our purposes
|
||||
is the `case customdecode.CustomExpressionDecoder:` clause, which uses
|
||||
a custom extension key type defined in this package to recognize when a
|
||||
component implementing this extension is checking to see if a target type
|
||||
has a custom decode implementation.
|
||||
|
||||
In the above case we've defined a type that decodes expressions as static
|
||||
keywords, so a keyword like `foo` would decode as an encapsulated `"foo"`
|
||||
string, while any other sort of expression like `"baz"` or `1 + 1` would
|
||||
return an error.
|
||||
|
||||
We could then use `keywordType` as a type constraint either for a function
|
||||
parameter or a `hcldec` attribute specification, which would require the
|
||||
argument for that function parameter or the expression for the matching
|
||||
attributes to be a static keyword, rather than an arbitrary expression.
|
||||
For example, in a `hcldec.AttrSpec`:
|
||||
|
||||
```go
|
||||
keywordSpec := &hcldec.AttrSpec{
|
||||
Name: "keyword",
|
||||
Type: keywordType,
|
||||
}
|
||||
```
|
||||
|
||||
The above would accept input like the following and would set its result to
|
||||
a `cty.Value` of `keywordType`, after decoding:
|
||||
|
||||
```hcl
|
||||
keyword = foo
|
||||
```
|
||||
|
||||
## The Expression and Expression Closure `cty` types
|
||||
|
||||
Building on the above, this package also includes two capsule types that use
|
||||
the above mechanism to allow calling applications to capture expressions
|
||||
directly and thus defer analysis to a later step, after initial decoding.
|
||||
|
||||
The `customdecode.ExpressionType` type encapsulates an `hcl.Expression` alone,
|
||||
for situations like our type constraint expression example above where it's
|
||||
the static structure of the expression we want to inspect, and thus any
|
||||
variables and functions defined in the evaluation context are irrelevant.
|
||||
|
||||
The `customdecode.ExpressionClosureType` type encapsulates a
|
||||
`*customdecode.ExpressionClosure` value, which binds the given expression to
|
||||
the `hcl.EvalContext` it was asked to evaluate against and thus allows the
|
||||
receiver of that result to later perform normal evaluation of the expression
|
||||
with all the same variables and functions that would've been available to it
|
||||
naturally.
|
||||
|
||||
Both of these types can be used as type constraints either for `hcldec`
|
||||
attribute specifications or for function arguments. Here's an example of
|
||||
`ExpressionClosureType` to implement a function that can evaluate
|
||||
an expression with some additional variables defined locally, which we'll
|
||||
call the `with(...)` function:
|
||||
|
||||
```go
|
||||
var WithFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "variables",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
{
|
||||
Name: "expression",
|
||||
Type: customdecode.ExpressionClosureType,
|
||||
},
|
||||
},
|
||||
Type: func(args []cty.Value) (cty.Type, error) {
|
||||
varsVal := args[0]
|
||||
exprVal := args[1]
|
||||
if !varsVal.Type().IsObjectType() {
|
||||
return cty.NilVal, function.NewArgErrorf(0, "must be an object defining local variables")
|
||||
}
|
||||
if !varsVal.IsKnown() {
|
||||
// We can't predict our result type until the variables object
|
||||
// is known.
|
||||
return cty.DynamicPseudoType, nil
|
||||
}
|
||||
vars := varsVal.AsValueMap()
|
||||
closure := customdecode.ExpressionClosureFromVal(exprVal)
|
||||
result, err := evalWithLocals(vars, closure)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
return result.Type(), nil
|
||||
},
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
varsVal := args[0]
|
||||
exprVal := args[1]
|
||||
vars := varsVal.AsValueMap()
|
||||
closure := customdecode.ExpressionClosureFromVal(exprVal)
|
||||
return evalWithLocals(vars, closure)
|
||||
},
|
||||
})
|
||||
|
||||
func evalWithLocals(locals map[string]cty.Value, closure *customdecode.ExpressionClosure) (cty.Value, error) {
|
||||
childCtx := closure.EvalContext.NewChild()
|
||||
childCtx.Variables = locals
|
||||
val, diags := closure.Expression.Value(childCtx)
|
||||
if diags.HasErrors() {
|
||||
return cty.NilVal, function.NewArgErrorf(1, "couldn't evaluate expression: %s", diags.Error())
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
```
|
||||
|
||||
If the above function were placed into an `hcl.EvalContext` as `with`, it
|
||||
could be used in a native syntax call to that function as follows:
|
||||
|
||||
```hcl
|
||||
foo = with({name = "Cory"}, "${greeting}, ${name}!")
|
||||
```
|
||||
|
||||
The above assumes a variable in the main context called `greeting`, to which
|
||||
the `with` function adds `name` before evaluating the expression given in
|
||||
its second argument. This makes that second argument context-sensitive -- it
|
||||
would behave differently if the user wrote the same thing somewhere else -- so
|
||||
this capability should be used with care to make sure it doesn't cause confusion
|
||||
for the end-users of your language.
|
||||
|
||||
There are some other examples of this capability to evaluate expressions in
|
||||
unusual ways in the `tryfunc` directory that is a sibling of this one.
|
56
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/customdecode.go
generated
vendored
Normal file
56
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/customdecode.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Package customdecode contains a HCL extension that allows, in certain
|
||||
// contexts, expression evaluation to be overridden by custom static analysis.
|
||||
//
|
||||
// This mechanism is only supported in certain specific contexts where
|
||||
// expressions are decoded with a specific target type in mind. For more
|
||||
// information, see the documentation on CustomExpressionDecoder.
|
||||
package customdecode
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
type customDecoderImpl int
|
||||
|
||||
// CustomExpressionDecoder is a value intended to be used as a cty capsule
|
||||
// type ExtensionData key for capsule types whose values are to be obtained
|
||||
// by static analysis of an expression rather than normal evaluation of that
|
||||
// expression.
|
||||
//
|
||||
// When a cooperating capsule type is asked for ExtensionData with this key,
|
||||
// it must return a non-nil CustomExpressionDecoderFunc value.
|
||||
//
|
||||
// This mechanism is not universally supported; instead, it's handled in a few
|
||||
// specific places where expressions are evaluated with the intent of producing
|
||||
// a cty.Value of a type given by the calling application.
|
||||
//
|
||||
// Specifically, this currently works for type constraints given in
|
||||
// hcldec.AttrSpec and hcldec.BlockAttrsSpec, and it works for arguments to
|
||||
// function calls in the HCL native syntax. HCL extensions implemented outside
|
||||
// of the main HCL module may also implement this; consult their own
|
||||
// documentation for details.
|
||||
const CustomExpressionDecoder = customDecoderImpl(1)
|
||||
|
||||
// CustomExpressionDecoderFunc is the type of value that must be returned by
|
||||
// a capsule type handling the key CustomExpressionDecoder in its ExtensionData
|
||||
// implementation.
|
||||
//
|
||||
// If no error diagnostics are returned, the result value MUST be of the
|
||||
// capsule type that the decoder function was derived from. If the returned
|
||||
// error diagnostics prevent producing a value at all, return cty.NilVal.
|
||||
type CustomExpressionDecoderFunc func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
||||
|
||||
// CustomExpressionDecoderForType takes any cty type and returns its
|
||||
// custom expression decoder implementation if it has one. If it is not a
|
||||
// capsule type or it does not implement a custom expression decoder, this
|
||||
// function returns nil.
|
||||
func CustomExpressionDecoderForType(ty cty.Type) CustomExpressionDecoderFunc {
|
||||
if !ty.IsCapsuleType() {
|
||||
return nil
|
||||
}
|
||||
if fn, ok := ty.CapsuleExtensionData(CustomExpressionDecoder).(CustomExpressionDecoderFunc); ok {
|
||||
return fn
|
||||
}
|
||||
return nil
|
||||
}
|
146
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/expression_type.go
generated
vendored
Normal file
146
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/expression_type.go
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
package customdecode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// ExpressionType is a cty capsule type that carries hcl.Expression values.
|
||||
//
|
||||
// This type implements custom decoding in the most general way possible: it
|
||||
// just captures whatever expression is given to it, with no further processing
|
||||
// whatsoever. It could therefore be useful in situations where an application
|
||||
// must defer processing of the expression content until a later step.
|
||||
//
|
||||
// ExpressionType only captures the expression, not the evaluation context it
|
||||
// was destined to be evaluated in. That means this type can be fine for
|
||||
// situations where the recipient of the value only intends to do static
|
||||
// analysis, but ExpressionClosureType is more appropriate in situations where
|
||||
// the recipient will eventually evaluate the given expression.
|
||||
var ExpressionType cty.Type
|
||||
|
||||
// ExpressionVal returns a new cty value of type ExpressionType, wrapping the
|
||||
// given expression.
|
||||
func ExpressionVal(expr hcl.Expression) cty.Value {
|
||||
return cty.CapsuleVal(ExpressionType, &expr)
|
||||
}
|
||||
|
||||
// ExpressionFromVal returns the expression encapsulated in the given value, or
|
||||
// panics if the value is not a known value of ExpressionType.
|
||||
func ExpressionFromVal(v cty.Value) hcl.Expression {
|
||||
if !v.Type().Equals(ExpressionType) {
|
||||
panic("value is not of ExpressionType")
|
||||
}
|
||||
ptr := v.EncapsulatedValue().(*hcl.Expression)
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// ExpressionClosureType is a cty capsule type that carries hcl.Expression
|
||||
// values along with their original evaluation contexts.
|
||||
//
|
||||
// This is similar to ExpressionType except that during custom decoding it
|
||||
// also captures the hcl.EvalContext that was provided, allowing callers to
|
||||
// evaluate the expression later in the same context where it would originally
|
||||
// have been evaluated, or a context derived from that one.
|
||||
var ExpressionClosureType cty.Type
|
||||
|
||||
// ExpressionClosure is the type encapsulated in ExpressionClosureType
|
||||
type ExpressionClosure struct {
|
||||
Expression hcl.Expression
|
||||
EvalContext *hcl.EvalContext
|
||||
}
|
||||
|
||||
// ExpressionClosureVal returns a new cty value of type ExpressionClosureType,
|
||||
// wrapping the given expression closure.
|
||||
func ExpressionClosureVal(closure *ExpressionClosure) cty.Value {
|
||||
return cty.CapsuleVal(ExpressionClosureType, closure)
|
||||
}
|
||||
|
||||
// Value evaluates the closure's expression using the closure's EvalContext,
|
||||
// returning the result.
|
||||
func (c *ExpressionClosure) Value() (cty.Value, hcl.Diagnostics) {
|
||||
return c.Expression.Value(c.EvalContext)
|
||||
}
|
||||
|
||||
// ExpressionClosureFromVal returns the expression closure encapsulated in the
|
||||
// given value, or panics if the value is not a known value of
|
||||
// ExpressionClosureType.
|
||||
//
|
||||
// The caller MUST NOT modify the returned closure or the EvalContext inside
|
||||
// it. To derive a new EvalContext, either create a child context or make
|
||||
// a copy.
|
||||
func ExpressionClosureFromVal(v cty.Value) *ExpressionClosure {
|
||||
if !v.Type().Equals(ExpressionClosureType) {
|
||||
panic("value is not of ExpressionClosureType")
|
||||
}
|
||||
return v.EncapsulatedValue().(*ExpressionClosure)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Getting hold of a reflect.Type for hcl.Expression is a bit tricky because
|
||||
// it's an interface type, but we can do it with some indirection.
|
||||
goExpressionType := reflect.TypeOf((*hcl.Expression)(nil)).Elem()
|
||||
|
||||
ExpressionType = cty.CapsuleWithOps("expression", goExpressionType, &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case CustomExpressionDecoder:
|
||||
return CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return ExpressionVal(expr), nil
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
TypeGoString: func(_ reflect.Type) string {
|
||||
return "customdecode.ExpressionType"
|
||||
},
|
||||
GoString: func(raw interface{}) string {
|
||||
exprPtr := raw.(*hcl.Expression)
|
||||
return fmt.Sprintf("customdecode.ExpressionVal(%#v)", *exprPtr)
|
||||
},
|
||||
RawEquals: func(a, b interface{}) bool {
|
||||
aPtr := a.(*hcl.Expression)
|
||||
bPtr := b.(*hcl.Expression)
|
||||
return reflect.DeepEqual(*aPtr, *bPtr)
|
||||
},
|
||||
})
|
||||
ExpressionClosureType = cty.CapsuleWithOps("expression closure", reflect.TypeOf(ExpressionClosure{}), &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case CustomExpressionDecoder:
|
||||
return CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return ExpressionClosureVal(&ExpressionClosure{
|
||||
Expression: expr,
|
||||
EvalContext: ctx,
|
||||
}), nil
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
TypeGoString: func(_ reflect.Type) string {
|
||||
return "customdecode.ExpressionClosureType"
|
||||
},
|
||||
GoString: func(raw interface{}) string {
|
||||
closure := raw.(*ExpressionClosure)
|
||||
return fmt.Sprintf("customdecode.ExpressionClosureVal(%#v)", closure)
|
||||
},
|
||||
RawEquals: func(a, b interface{}) bool {
|
||||
closureA := a.(*ExpressionClosure)
|
||||
closureB := b.(*ExpressionClosure)
|
||||
// The expression itself compares by deep equality, but EvalContexts
|
||||
// conventionally compare by pointer identity, so we'll comply
|
||||
// with both conventions here by testing them separately.
|
||||
return closureA.EvalContext == closureB.EvalContext &&
|
||||
reflect.DeepEqual(closureA.Expression, closureB.Expression)
|
||||
},
|
||||
})
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
# "Try" and "can" functions
|
||||
|
||||
This Go package contains two `cty` functions intended for use in an
|
||||
`hcl.EvalContext` when evaluating HCL native syntax expressions.
|
||||
|
||||
The first function `try` attempts to evaluate each of its argument expressions
|
||||
in order until one produces a result without any errors.
|
||||
|
||||
```hcl
|
||||
try(non_existent_variable, 2) # returns 2
|
||||
```
|
||||
|
||||
If none of the expressions succeed, the function call fails with all of the
|
||||
errors it encountered.
|
||||
|
||||
The second function `can` is similar except that it ignores the result of
|
||||
the given expression altogether and simply returns `true` if the expression
|
||||
produced a successful result or `false` if it produced errors.
|
||||
|
||||
Both of these are primarily intended for working with deep data structures
|
||||
which might not have a dependable shape. For example, we can use `try` to
|
||||
attempt to fetch a value from deep inside a data structure but produce a
|
||||
default value if any step of the traversal fails:
|
||||
|
||||
```hcl
|
||||
result = try(foo.deep[0].lots.of["traversals"], null)
|
||||
```
|
||||
|
||||
The final result to `try` should generally be some sort of constant value that
|
||||
will always evaluate successfully.
|
||||
|
||||
## Using these functions
|
||||
|
||||
Languages built on HCL can make `try` and `can` available to user code by
|
||||
exporting them in the `hcl.EvalContext` used for expression evaluation:
|
||||
|
||||
```go
|
||||
ctx := &hcl.EvalContext{
|
||||
Functions: map[string]function.Function{
|
||||
"try": tryfunc.TryFunc,
|
||||
"can": tryfunc.CanFunc,
|
||||
},
|
||||
}
|
||||
```
|
|
@ -0,0 +1,150 @@
|
|||
// Package tryfunc contains some optional functions that can be exposed in
|
||||
// HCL-based languages to allow authors to test whether a particular expression
|
||||
// can succeed and take dynamic action based on that result.
|
||||
//
|
||||
// These functions are implemented in terms of the customdecode extension from
|
||||
// the sibling directory "customdecode", and so they are only useful when
|
||||
// used within an HCL EvalContext. Other systems using cty functions are
|
||||
// unlikely to support the HCL-specific "customdecode" extension.
|
||||
package tryfunc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// TryFunc is a variadic function that tries to evaluate all of is arguments
|
||||
// in sequence until one succeeds, in which case it returns that result, or
|
||||
// returns an error if none of them succeed.
|
||||
var TryFunc function.Function
|
||||
|
||||
// CanFunc tries to evaluate the expression given in its first argument.
|
||||
var CanFunc function.Function
|
||||
|
||||
func init() {
|
||||
TryFunc = function.New(&function.Spec{
|
||||
VarParam: &function.Parameter{
|
||||
Name: "expressions",
|
||||
Type: customdecode.ExpressionClosureType,
|
||||
},
|
||||
Type: func(args []cty.Value) (cty.Type, error) {
|
||||
v, err := try(args)
|
||||
if err != nil {
|
||||
return cty.NilType, err
|
||||
}
|
||||
return v.Type(), nil
|
||||
},
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return try(args)
|
||||
},
|
||||
})
|
||||
CanFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "expression",
|
||||
Type: customdecode.ExpressionClosureType,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
return can(args[0])
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func try(args []cty.Value) (cty.Value, error) {
|
||||
if len(args) == 0 {
|
||||
return cty.NilVal, errors.New("at least one argument is required")
|
||||
}
|
||||
|
||||
// We'll collect up all of the diagnostics we encounter along the way
|
||||
// and report them all if none of the expressions succeed, so that the
|
||||
// user might get some hints on how to make at least one succeed.
|
||||
var diags hcl.Diagnostics
|
||||
for _, arg := range args {
|
||||
closure := customdecode.ExpressionClosureFromVal(arg)
|
||||
if dependsOnUnknowns(closure.Expression, closure.EvalContext) {
|
||||
// We can't safely decide if this expression will succeed yet,
|
||||
// and so our entire result must be unknown until we have
|
||||
// more information.
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
|
||||
v, moreDiags := closure.Value()
|
||||
diags = append(diags, moreDiags...)
|
||||
if moreDiags.HasErrors() {
|
||||
continue // try the next one, if there is one to try
|
||||
}
|
||||
return v, nil // ignore any accumulated diagnostics if one succeeds
|
||||
}
|
||||
|
||||
// If we fall out here then none of the expressions succeeded, and so
|
||||
// we must have at least one diagnostic and we'll return all of them
|
||||
// so that the user can see the errors related to whichever one they
|
||||
// were expecting to have succeeded in this case.
|
||||
//
|
||||
// Because our function must return a single error value rather than
|
||||
// diagnostics, we'll construct a suitable error message string
|
||||
// that will make sense in the context of the function call failure
|
||||
// diagnostic HCL will eventually wrap this in.
|
||||
var buf strings.Builder
|
||||
buf.WriteString("no expression succeeded:\n")
|
||||
for _, diag := range diags {
|
||||
if diag.Subject != nil {
|
||||
buf.WriteString(fmt.Sprintf("- %s (at %s)\n %s\n", diag.Summary, diag.Subject, diag.Detail))
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf("- %s\n %s\n", diag.Summary, diag.Detail))
|
||||
}
|
||||
}
|
||||
buf.WriteString("\nAt least one expression must produce a successful result")
|
||||
return cty.NilVal, errors.New(buf.String())
|
||||
}
|
||||
|
||||
func can(arg cty.Value) (cty.Value, error) {
|
||||
closure := customdecode.ExpressionClosureFromVal(arg)
|
||||
if dependsOnUnknowns(closure.Expression, closure.EvalContext) {
|
||||
// Can't decide yet, then.
|
||||
return cty.UnknownVal(cty.Bool), nil
|
||||
}
|
||||
|
||||
_, diags := closure.Value()
|
||||
if diags.HasErrors() {
|
||||
return cty.False, nil
|
||||
}
|
||||
return cty.True, nil
|
||||
}
|
||||
|
||||
// dependsOnUnknowns returns true if any of the variables that the given
|
||||
// expression might access are unknown values or contain unknown values.
|
||||
//
|
||||
// This is a conservative result that prefers to return true if there's any
|
||||
// chance that the expression might derive from an unknown value during its
|
||||
// evaluation; it is likely to produce false-positives for more complex
|
||||
// expressions involving deep data structures.
|
||||
func dependsOnUnknowns(expr hcl.Expression, ctx *hcl.EvalContext) bool {
|
||||
for _, traversal := range expr.Variables() {
|
||||
val, diags := traversal.TraverseAbs(ctx)
|
||||
if diags.HasErrors() {
|
||||
// If the traversal returned a definitive error then it must
|
||||
// not traverse through any unknowns.
|
||||
continue
|
||||
}
|
||||
if !val.IsWhollyKnown() {
|
||||
// The value will be unknown if either it refers directly to
|
||||
// an unknown value or if the traversal moves through an unknown
|
||||
// collection. We're using IsWhollyKnown, so this also catches
|
||||
// situations where the traversal refers to a compound data
|
||||
// structure that contains any unknown values. That's important,
|
||||
// because during evaluation the expression might evaluate more
|
||||
// deeply into this structure and encounter the unknowns.
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
# HCL Type Expressions Extension
|
||||
|
||||
This HCL extension defines a convention for describing HCL types using function
|
||||
call and variable reference syntax, allowing configuration formats to include
|
||||
type information provided by users.
|
||||
|
||||
The type syntax is processed statically from a hcl.Expression, so it cannot
|
||||
use any of the usual language operators. This is similar to type expressions
|
||||
in statically-typed programming languages.
|
||||
|
||||
```hcl
|
||||
variable "example" {
|
||||
type = list(string)
|
||||
}
|
||||
```
|
||||
|
||||
The extension is built using the `hcl.ExprAsKeyword` and `hcl.ExprCall`
|
||||
functions, and so it relies on the underlying syntax to define how "keyword"
|
||||
and "call" are interpreted. The above shows how they are interpreted in
|
||||
the HCL native syntax, while the following shows the same information
|
||||
expressed in JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"variable": {
|
||||
"example": {
|
||||
"type": "list(string)"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notice that since we have additional contextual information that we intend
|
||||
to allow only calls and keywords the JSON syntax is able to parse the given
|
||||
string directly as an expression, rather than as a template as would be
|
||||
the case for normal expression evaluation.
|
||||
|
||||
For more information, see [the godoc reference](http://godoc.org/github.com/hashicorp/hcl/v2/ext/typeexpr).
|
||||
|
||||
## Type Expression Syntax
|
||||
|
||||
When expressed in the native syntax, the following expressions are permitted
|
||||
in a type expression:
|
||||
|
||||
* `string` - string
|
||||
* `bool` - boolean
|
||||
* `number` - number
|
||||
* `any` - `cty.DynamicPseudoType` (in function `TypeConstraint` only)
|
||||
* `list(<type_expr>)` - list of the type given as an argument
|
||||
* `set(<type_expr>)` - set of the type given as an argument
|
||||
* `map(<type_expr>)` - map of the type given as an argument
|
||||
* `tuple([<type_exprs...>])` - tuple with the element types given in the single list argument
|
||||
* `object({<attr_name>=<type_expr>, ...}` - object with the attributes and corresponding types given in the single map argument
|
||||
|
||||
For example:
|
||||
|
||||
* `list(string)`
|
||||
* `object({name=string,age=number})`
|
||||
* `map(object({name=string,age=number}))`
|
||||
|
||||
Note that the object constructor syntax is not fully-general for all possible
|
||||
object types because it requires the attribute names to be valid identifiers.
|
||||
In practice it is expected that any time an object type is being fixed for
|
||||
type checking it will be one that has identifiers as its attributes; object
|
||||
types with weird attributes generally show up only from arbitrary object
|
||||
constructors in configuration files, which are usually treated either as maps
|
||||
or as the dynamic pseudo-type.
|
||||
|
||||
## Type Constraints as Values
|
||||
|
||||
Along with defining a convention for writing down types using HCL expression
|
||||
constructs, this package also includes a mechanism for representing types as
|
||||
values that can be used as data within an HCL-based language.
|
||||
|
||||
`typeexpr.TypeConstraintType` is a
|
||||
[`cty` capsule type](https://github.com/zclconf/go-cty/blob/master/docs/types.md#capsule-types)
|
||||
that encapsulates `cty.Type` values. You can construct such a value directly
|
||||
using the `TypeConstraintVal` function:
|
||||
|
||||
```go
|
||||
tyVal := typeexpr.TypeConstraintVal(cty.String)
|
||||
|
||||
// We can unpack the type from a value using TypeConstraintFromVal
|
||||
ty := typeExpr.TypeConstraintFromVal(tyVal)
|
||||
```
|
||||
|
||||
However, the primary purpose of `typeexpr.TypeConstraintType` is to be
|
||||
specified as the type constraint for an argument, in which case it serves
|
||||
as a signal for HCL to treat the argument expression as a type constraint
|
||||
expression as defined above, rather than as a normal value expression.
|
||||
|
||||
"An argument" in the above in practice means the following two locations:
|
||||
|
||||
* As the type constraint for a parameter of a cty function that will be
|
||||
used in an `hcl.EvalContext`. In that case, function calls in the HCL
|
||||
native expression syntax will require the argument to be valid type constraint
|
||||
expression syntax and the function implementation will receive a
|
||||
`TypeConstraintType` value as the argument value for that parameter.
|
||||
|
||||
* As the type constraint for a `hcldec.AttrSpec` or `hcldec.BlockAttrsSpec`
|
||||
when decoding an HCL body using `hcldec`. In that case, the attributes
|
||||
with that type constraint will be required to be valid type constraint
|
||||
expression syntax and the result will be a `TypeConstraintType` value.
|
||||
|
||||
Note that the special handling of these arguments means that an argument
|
||||
marked in this way must use the type constraint syntax directly. It is not
|
||||
valid to pass in a value of `TypeConstraintType` that has been obtained
|
||||
dynamically via some other expression result.
|
||||
|
||||
`TypeConstraintType` is provided with the intent of using it internally within
|
||||
application code when incorporating type constraint expression syntax into
|
||||
an HCL-based language, not to be used for dynamic "programming with types". A
|
||||
calling application could support programming with types by defining its _own_
|
||||
capsule type, but that is not the purpose of `TypeConstraintType`.
|
||||
|
||||
## The "convert" `cty` Function
|
||||
|
||||
Building on the `TypeConstraintType` described in the previous section, this
|
||||
package also provides `typeexpr.ConvertFunc` which is a cty function that
|
||||
can be placed into a `cty.EvalContext` (conventionally named "convert") in
|
||||
order to provide a general type conversion function in an HCL-based language:
|
||||
|
||||
```hcl
|
||||
foo = convert("true", bool)
|
||||
```
|
||||
|
||||
The second parameter uses the mechanism described in the previous section to
|
||||
require its argument to be a type constraint expression rather than a value
|
||||
expression. In doing so, it allows converting with any type constraint that
|
||||
can be expressed in this package's type constraint syntax. In the above example,
|
||||
the `foo` argument would receive a boolean true, or `cty.True` in `cty` terms.
|
||||
|
||||
The target type constraint must always be provided statically using inline
|
||||
type constraint syntax. There is no way to _dynamically_ select a type
|
||||
constraint using this function.
|
|
@ -0,0 +1,11 @@
|
|||
// Package typeexpr extends HCL with a convention for describing HCL types
|
||||
// within configuration files.
|
||||
//
|
||||
// The type syntax is processed statically from a hcl.Expression, so it cannot
|
||||
// use any of the usual language operators. This is similar to type expressions
|
||||
// in statically-typed programming languages.
|
||||
//
|
||||
// variable "example" {
|
||||
// type = list(string)
|
||||
// }
|
||||
package typeexpr
|
|
@ -0,0 +1,196 @@
|
|||
package typeexpr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
const invalidTypeSummary = "Invalid type specification"
|
||||
|
||||
// getType is the internal implementation of both Type and TypeConstraint,
|
||||
// using the passed flag to distinguish. When constraint is false, the "any"
|
||||
// keyword will produce an error.
|
||||
func getType(expr hcl.Expression, constraint bool) (cty.Type, hcl.Diagnostics) {
|
||||
// First we'll try for one of our keywords
|
||||
kw := hcl.ExprAsKeyword(expr)
|
||||
switch kw {
|
||||
case "bool":
|
||||
return cty.Bool, nil
|
||||
case "string":
|
||||
return cty.String, nil
|
||||
case "number":
|
||||
return cty.Number, nil
|
||||
case "any":
|
||||
if constraint {
|
||||
return cty.DynamicPseudoType, nil
|
||||
}
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: fmt.Sprintf("The keyword %q cannot be used in this type specification: an exact type is required.", kw),
|
||||
Subject: expr.Range().Ptr(),
|
||||
}}
|
||||
case "list", "map", "set":
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: fmt.Sprintf("The %s type constructor requires one argument specifying the element type.", kw),
|
||||
Subject: expr.Range().Ptr(),
|
||||
}}
|
||||
case "object":
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: "The object type constructor requires one argument specifying the attribute types and values as a map.",
|
||||
Subject: expr.Range().Ptr(),
|
||||
}}
|
||||
case "tuple":
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: "The tuple type constructor requires one argument specifying the element types as a list.",
|
||||
Subject: expr.Range().Ptr(),
|
||||
}}
|
||||
case "":
|
||||
// okay! we'll fall through and try processing as a call, then.
|
||||
default:
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: fmt.Sprintf("The keyword %q is not a valid type specification.", kw),
|
||||
Subject: expr.Range().Ptr(),
|
||||
}}
|
||||
}
|
||||
|
||||
// If we get down here then our expression isn't just a keyword, so we'll
|
||||
// try to process it as a call instead.
|
||||
call, diags := hcl.ExprCall(expr)
|
||||
if diags.HasErrors() {
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: "A type specification is either a primitive type keyword (bool, number, string) or a complex type constructor call, like list(string).",
|
||||
Subject: expr.Range().Ptr(),
|
||||
}}
|
||||
}
|
||||
|
||||
switch call.Name {
|
||||
case "bool", "string", "number", "any":
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: fmt.Sprintf("Primitive type keyword %q does not expect arguments.", call.Name),
|
||||
Subject: &call.ArgsRange,
|
||||
}}
|
||||
}
|
||||
|
||||
if len(call.Arguments) != 1 {
|
||||
contextRange := call.ArgsRange
|
||||
subjectRange := call.ArgsRange
|
||||
if len(call.Arguments) > 1 {
|
||||
// If we have too many arguments (as opposed to too _few_) then
|
||||
// we'll highlight the extraneous arguments as the diagnostic
|
||||
// subject.
|
||||
subjectRange = hcl.RangeBetween(call.Arguments[1].Range(), call.Arguments[len(call.Arguments)-1].Range())
|
||||
}
|
||||
|
||||
switch call.Name {
|
||||
case "list", "set", "map":
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: fmt.Sprintf("The %s type constructor requires one argument specifying the element type.", call.Name),
|
||||
Subject: &subjectRange,
|
||||
Context: &contextRange,
|
||||
}}
|
||||
case "object":
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: "The object type constructor requires one argument specifying the attribute types and values as a map.",
|
||||
Subject: &subjectRange,
|
||||
Context: &contextRange,
|
||||
}}
|
||||
case "tuple":
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: "The tuple type constructor requires one argument specifying the element types as a list.",
|
||||
Subject: &subjectRange,
|
||||
Context: &contextRange,
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
switch call.Name {
|
||||
|
||||
case "list":
|
||||
ety, diags := getType(call.Arguments[0], constraint)
|
||||
return cty.List(ety), diags
|
||||
case "set":
|
||||
ety, diags := getType(call.Arguments[0], constraint)
|
||||
return cty.Set(ety), diags
|
||||
case "map":
|
||||
ety, diags := getType(call.Arguments[0], constraint)
|
||||
return cty.Map(ety), diags
|
||||
case "object":
|
||||
attrDefs, diags := hcl.ExprMap(call.Arguments[0])
|
||||
if diags.HasErrors() {
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: "Object type constructor requires a map whose keys are attribute names and whose values are the corresponding attribute types.",
|
||||
Subject: call.Arguments[0].Range().Ptr(),
|
||||
Context: expr.Range().Ptr(),
|
||||
}}
|
||||
}
|
||||
|
||||
atys := make(map[string]cty.Type)
|
||||
for _, attrDef := range attrDefs {
|
||||
attrName := hcl.ExprAsKeyword(attrDef.Key)
|
||||
if attrName == "" {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: "Object constructor map keys must be attribute names.",
|
||||
Subject: attrDef.Key.Range().Ptr(),
|
||||
Context: expr.Range().Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
aty, attrDiags := getType(attrDef.Value, constraint)
|
||||
diags = append(diags, attrDiags...)
|
||||
atys[attrName] = aty
|
||||
}
|
||||
return cty.Object(atys), diags
|
||||
case "tuple":
|
||||
elemDefs, diags := hcl.ExprList(call.Arguments[0])
|
||||
if diags.HasErrors() {
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: "Tuple type constructor requires a list of element types.",
|
||||
Subject: call.Arguments[0].Range().Ptr(),
|
||||
Context: expr.Range().Ptr(),
|
||||
}}
|
||||
}
|
||||
etys := make([]cty.Type, len(elemDefs))
|
||||
for i, defExpr := range elemDefs {
|
||||
ety, elemDiags := getType(defExpr, constraint)
|
||||
diags = append(diags, elemDiags...)
|
||||
etys[i] = ety
|
||||
}
|
||||
return cty.Tuple(etys), diags
|
||||
default:
|
||||
// Can't access call.Arguments in this path because we've not validated
|
||||
// that it contains exactly one expression here.
|
||||
return cty.DynamicPseudoType, hcl.Diagnostics{{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: invalidTypeSummary,
|
||||
Detail: fmt.Sprintf("Keyword %q is not a valid type constructor.", call.Name),
|
||||
Subject: expr.Range().Ptr(),
|
||||
}}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package typeexpr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// Type attempts to process the given expression as a type expression and, if
|
||||
// successful, returns the resulting type. If unsuccessful, error diagnostics
|
||||
// are returned.
|
||||
func Type(expr hcl.Expression) (cty.Type, hcl.Diagnostics) {
|
||||
return getType(expr, false)
|
||||
}
|
||||
|
||||
// TypeConstraint attempts to parse the given expression as a type constraint
|
||||
// and, if successful, returns the resulting type. If unsuccessful, error
|
||||
// diagnostics are returned.
|
||||
//
|
||||
// A type constraint has the same structure as a type, but it additionally
|
||||
// allows the keyword "any" to represent cty.DynamicPseudoType, which is often
|
||||
// used as a wildcard in type checking and type conversion operations.
|
||||
func TypeConstraint(expr hcl.Expression) (cty.Type, hcl.Diagnostics) {
|
||||
return getType(expr, true)
|
||||
}
|
||||
|
||||
// TypeString returns a string rendering of the given type as it would be
|
||||
// expected to appear in the HCL native syntax.
|
||||
//
|
||||
// This is primarily intended for showing types to the user in an application
|
||||
// that uses typexpr, where the user can be assumed to be familiar with the
|
||||
// type expression syntax. In applications that do not use typeexpr these
|
||||
// results may be confusing to the user and so type.FriendlyName may be
|
||||
// preferable, even though it's less precise.
|
||||
//
|
||||
// TypeString produces reasonable results only for types like what would be
|
||||
// produced by the Type and TypeConstraint functions. In particular, it cannot
|
||||
// support capsule types.
|
||||
func TypeString(ty cty.Type) string {
|
||||
// Easy cases first
|
||||
switch ty {
|
||||
case cty.String:
|
||||
return "string"
|
||||
case cty.Bool:
|
||||
return "bool"
|
||||
case cty.Number:
|
||||
return "number"
|
||||
case cty.DynamicPseudoType:
|
||||
return "any"
|
||||
}
|
||||
|
||||
if ty.IsCapsuleType() {
|
||||
panic("TypeString does not support capsule types")
|
||||
}
|
||||
|
||||
if ty.IsCollectionType() {
|
||||
ety := ty.ElementType()
|
||||
etyString := TypeString(ety)
|
||||
switch {
|
||||
case ty.IsListType():
|
||||
return fmt.Sprintf("list(%s)", etyString)
|
||||
case ty.IsSetType():
|
||||
return fmt.Sprintf("set(%s)", etyString)
|
||||
case ty.IsMapType():
|
||||
return fmt.Sprintf("map(%s)", etyString)
|
||||
default:
|
||||
// Should never happen because the above is exhaustive
|
||||
panic("unsupported collection type")
|
||||
}
|
||||
}
|
||||
|
||||
if ty.IsObjectType() {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("object({")
|
||||
atys := ty.AttributeTypes()
|
||||
names := make([]string, 0, len(atys))
|
||||
for name := range atys {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
first := true
|
||||
for _, name := range names {
|
||||
aty := atys[name]
|
||||
if !first {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
if !hclsyntax.ValidIdentifier(name) {
|
||||
// Should never happen for any type produced by this package,
|
||||
// but we'll do something reasonable here just so we don't
|
||||
// produce garbage if someone gives us a hand-assembled object
|
||||
// type that has weird attribute names.
|
||||
// Using Go-style quoting here isn't perfect, since it doesn't
|
||||
// exactly match HCL syntax, but it's fine for an edge-case.
|
||||
buf.WriteString(fmt.Sprintf("%q", name))
|
||||
} else {
|
||||
buf.WriteString(name)
|
||||
}
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(TypeString(aty))
|
||||
first = false
|
||||
}
|
||||
buf.WriteString("})")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
if ty.IsTupleType() {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("tuple([")
|
||||
etys := ty.TupleElementTypes()
|
||||
first := true
|
||||
for _, ety := range etys {
|
||||
if !first {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
buf.WriteString(TypeString(ety))
|
||||
first = false
|
||||
}
|
||||
buf.WriteString("])")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Should never happen because we covered all cases above.
|
||||
panic(fmt.Errorf("unsupported type %#v", ty))
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package typeexpr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// TypeConstraintType is a cty capsule type that allows cty type constraints to
|
||||
// be used as values.
|
||||
//
|
||||
// If TypeConstraintType is used in a context supporting the
|
||||
// customdecode.CustomExpressionDecoder extension then it will implement
|
||||
// expression decoding using the TypeConstraint function, thus allowing
|
||||
// type expressions to be used in contexts where value expressions might
|
||||
// normally be expected, such as in arguments to function calls.
|
||||
var TypeConstraintType cty.Type
|
||||
|
||||
// TypeConstraintVal constructs a cty.Value whose type is
|
||||
// TypeConstraintType.
|
||||
func TypeConstraintVal(ty cty.Type) cty.Value {
|
||||
return cty.CapsuleVal(TypeConstraintType, &ty)
|
||||
}
|
||||
|
||||
// TypeConstraintFromVal extracts the type from a cty.Value of
|
||||
// TypeConstraintType that was previously constructed using TypeConstraintVal.
|
||||
//
|
||||
// If the given value isn't a known, non-null value of TypeConstraintType
|
||||
// then this function will panic.
|
||||
func TypeConstraintFromVal(v cty.Value) cty.Type {
|
||||
if !v.Type().Equals(TypeConstraintType) {
|
||||
panic("value is not of TypeConstraintType")
|
||||
}
|
||||
ptr := v.EncapsulatedValue().(*cty.Type)
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// ConvertFunc is a cty function that implements type conversions.
|
||||
//
|
||||
// Its signature is as follows:
|
||||
// convert(value, type_constraint)
|
||||
//
|
||||
// ...where type_constraint is a type constraint expression as defined by
|
||||
// typeexpr.TypeConstraint.
|
||||
//
|
||||
// It relies on HCL's customdecode extension and so it's not suitable for use
|
||||
// in non-HCL contexts or if you are using a HCL syntax implementation that
|
||||
// does not support customdecode for function arguments. However, it _is_
|
||||
// supported for function calls in the HCL native expression syntax.
|
||||
var ConvertFunc function.Function
|
||||
|
||||
func init() {
|
||||
TypeConstraintType = cty.CapsuleWithOps("type constraint", reflect.TypeOf(cty.Type{}), &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case customdecode.CustomExpressionDecoder:
|
||||
return customdecode.CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
ty, diags := TypeConstraint(expr)
|
||||
if diags.HasErrors() {
|
||||
return cty.NilVal, diags
|
||||
}
|
||||
return TypeConstraintVal(ty), nil
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
TypeGoString: func(_ reflect.Type) string {
|
||||
return "typeexpr.TypeConstraintType"
|
||||
},
|
||||
GoString: func(raw interface{}) string {
|
||||
tyPtr := raw.(*cty.Type)
|
||||
return fmt.Sprintf("typeexpr.TypeConstraintVal(%#v)", *tyPtr)
|
||||
},
|
||||
RawEquals: func(a, b interface{}) bool {
|
||||
aPtr := a.(*cty.Type)
|
||||
bPtr := b.(*cty.Type)
|
||||
return (*aPtr).Equals(*bPtr)
|
||||
},
|
||||
})
|
||||
|
||||
ConvertFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "value",
|
||||
Type: cty.DynamicPseudoType,
|
||||
AllowNull: true,
|
||||
AllowDynamicType: true,
|
||||
},
|
||||
{
|
||||
Name: "type",
|
||||
Type: TypeConstraintType,
|
||||
},
|
||||
},
|
||||
Type: func(args []cty.Value) (cty.Type, error) {
|
||||
wantTypePtr := args[1].EncapsulatedValue().(*cty.Type)
|
||||
got, err := convert.Convert(args[0], *wantTypePtr)
|
||||
if err != nil {
|
||||
return cty.NilType, function.NewArgError(0, err)
|
||||
}
|
||||
return got.Type(), nil
|
||||
},
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
v, err := convert.Convert(args[0], retType)
|
||||
if err != nil {
|
||||
return cty.NilVal, function.NewArgError(0, err)
|
||||
}
|
||||
return v, nil
|
||||
},
|
||||
})
|
||||
}
|
|
@ -6,7 +6,7 @@ require (
|
|||
github.com/apparentlymart/go-textseg v1.0.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/go-test/deep v1.0.3
|
||||
github.com/google/go-cmp v0.2.0
|
||||
github.com/google/go-cmp v0.3.1
|
||||
github.com/kr/pretty v0.1.0
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
|
||||
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7
|
||||
|
@ -14,7 +14,7 @@ require (
|
|||
github.com/sergi/go-diff v1.0.0
|
||||
github.com/spf13/pflag v1.0.2
|
||||
github.com/stretchr/testify v1.2.2 // indirect
|
||||
github.com/zclconf/go-cty v1.1.0
|
||||
github.com/zclconf/go-cty v1.2.0
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
|
||||
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
|
|
|
@ -9,8 +9,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
|
@ -29,8 +29,8 @@ github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw=
|
||||
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI=
|
||||
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
|
|
@ -147,7 +147,9 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
|
|||
|
||||
if len(blocks) == 0 {
|
||||
if isSlice || isPtr {
|
||||
if val.Field(fieldIdx).IsNil() {
|
||||
val.Field(fieldIdx).Set(reflect.Zero(field.Type))
|
||||
}
|
||||
} else {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
|
@ -166,11 +168,20 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
|
|||
if isPtr {
|
||||
elemType = reflect.PtrTo(ty)
|
||||
}
|
||||
sli := reflect.MakeSlice(reflect.SliceOf(elemType), len(blocks), len(blocks))
|
||||
sli := val.Field(fieldIdx)
|
||||
if sli.IsNil() {
|
||||
sli = reflect.MakeSlice(reflect.SliceOf(elemType), len(blocks), len(blocks))
|
||||
}
|
||||
|
||||
for i, block := range blocks {
|
||||
if isPtr {
|
||||
v := reflect.New(ty)
|
||||
if i >= sli.Len() {
|
||||
sli = reflect.Append(sli, reflect.New(ty))
|
||||
}
|
||||
v := sli.Index(i)
|
||||
if v.IsNil() {
|
||||
v = reflect.New(ty)
|
||||
}
|
||||
diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
|
||||
sli.Index(i).Set(v)
|
||||
} else {
|
||||
|
@ -178,12 +189,19 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
|
|||
}
|
||||
}
|
||||
|
||||
if sli.Len() > len(blocks) {
|
||||
sli.SetLen(len(blocks))
|
||||
}
|
||||
|
||||
val.Field(fieldIdx).Set(sli)
|
||||
|
||||
default:
|
||||
block := blocks[0]
|
||||
if isPtr {
|
||||
v := reflect.New(ty)
|
||||
v := val.Field(fieldIdx)
|
||||
if v.IsNil() {
|
||||
v = reflect.New(ty)
|
||||
}
|
||||
diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
|
||||
val.Field(fieldIdx).Set(v)
|
||||
} else {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"sort"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
|
@ -193,6 +194,14 @@ func (s *AttrSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ct
|
|||
return cty.NullVal(s.Type), nil
|
||||
}
|
||||
|
||||
if decodeFn := customdecode.CustomExpressionDecoderForType(s.Type); decodeFn != nil {
|
||||
v, diags := decodeFn(attr.Expr, ctx)
|
||||
if v == cty.NilVal {
|
||||
v = cty.UnknownVal(s.Type)
|
||||
}
|
||||
return v, diags
|
||||
}
|
||||
|
||||
val, diags := attr.Expr.Value(ctx)
|
||||
|
||||
convVal, err := convert.Convert(val, s.Type)
|
||||
|
@ -204,8 +213,10 @@ func (s *AttrSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ct
|
|||
"Inappropriate value for attribute %q: %s.",
|
||||
s.Name, err.Error(),
|
||||
),
|
||||
Subject: attr.Expr.StartRange().Ptr(),
|
||||
Context: hcl.RangeBetween(attr.NameRange, attr.Expr.StartRange()).Ptr(),
|
||||
Subject: attr.Expr.Range().Ptr(),
|
||||
Context: hcl.RangeBetween(attr.NameRange, attr.Expr.Range()).Ptr(),
|
||||
Expression: attr.Expr,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
// We'll return an unknown value of the _correct_ type so that the
|
||||
// incomplete result can still be used for some analysis use-cases.
|
||||
|
@ -1221,6 +1232,16 @@ func (s *BlockAttrsSpec) decode(content *hcl.BodyContent, blockLabels []blockLab
|
|||
|
||||
vals := make(map[string]cty.Value, len(attrs))
|
||||
for name, attr := range attrs {
|
||||
if decodeFn := customdecode.CustomExpressionDecoderForType(s.ElementType); decodeFn != nil {
|
||||
attrVal, attrDiags := decodeFn(attr.Expr, ctx)
|
||||
diags = append(diags, attrDiags...)
|
||||
if attrVal == cty.NilVal {
|
||||
attrVal = cty.UnknownVal(s.ElementType)
|
||||
}
|
||||
vals[name] = attrVal
|
||||
continue
|
||||
}
|
||||
|
||||
attrVal, attrDiags := attr.Expr.Value(ctx)
|
||||
diags = append(diags, attrDiags...)
|
||||
|
||||
|
@ -1231,6 +1252,9 @@ func (s *BlockAttrsSpec) decode(content *hcl.BodyContent, blockLabels []blockLab
|
|||
Summary: "Invalid attribute value",
|
||||
Detail: fmt.Sprintf("Invalid value for attribute of %q block: %s.", s.TypeName, err),
|
||||
Subject: attr.Expr.Range().Ptr(),
|
||||
Context: hcl.RangeBetween(attr.NameRange, attr.Expr.Range()).Ptr(),
|
||||
Expression: attr.Expr,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
attrVal = cty.UnknownVal(s.ElementType)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/ext/customdecode"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
|
@ -350,13 +351,24 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
|
|||
param = varParam
|
||||
}
|
||||
|
||||
val, argDiags := argExpr.Value(ctx)
|
||||
var val cty.Value
|
||||
if decodeFn := customdecode.CustomExpressionDecoderForType(param.Type); decodeFn != nil {
|
||||
var argDiags hcl.Diagnostics
|
||||
val, argDiags = decodeFn(argExpr, ctx)
|
||||
diags = append(diags, argDiags...)
|
||||
if val == cty.NilVal {
|
||||
val = cty.UnknownVal(param.Type)
|
||||
}
|
||||
} else {
|
||||
var argDiags hcl.Diagnostics
|
||||
val, argDiags = argExpr.Value(ctx)
|
||||
if len(argDiags) > 0 {
|
||||
diags = append(diags, argDiags...)
|
||||
}
|
||||
|
||||
// Try to convert our value to the parameter type
|
||||
val, err := convert.Convert(val, param.Type)
|
||||
var err error
|
||||
val, err = convert.Convert(val, param.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
|
@ -371,6 +383,7 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
|
|||
EvalContext: ctx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
argVals[i] = val
|
||||
}
|
||||
|
@ -617,6 +630,7 @@ type IndexExpr struct {
|
|||
|
||||
SrcRange hcl.Range
|
||||
OpenRange hcl.Range
|
||||
BracketRange hcl.Range
|
||||
}
|
||||
|
||||
func (e *IndexExpr) walkChildNodes(w internalWalkFunc) {
|
||||
|
@ -631,7 +645,7 @@ func (e *IndexExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|||
diags = append(diags, collDiags...)
|
||||
diags = append(diags, keyDiags...)
|
||||
|
||||
val, indexDiags := hcl.Index(coll, key, &e.SrcRange)
|
||||
val, indexDiags := hcl.Index(coll, key, &e.BracketRange)
|
||||
setDiagEvalContext(indexDiags, e, ctx)
|
||||
diags = append(diags, indexDiags...)
|
||||
return val, diags
|
||||
|
|
|
@ -760,7 +760,7 @@ Traversal:
|
|||
Each: travExpr,
|
||||
Item: itemExpr,
|
||||
|
||||
SrcRange: hcl.RangeBetween(dot.Range, lastRange),
|
||||
SrcRange: hcl.RangeBetween(from.Range(), lastRange),
|
||||
MarkerRange: hcl.RangeBetween(dot.Range, marker.Range),
|
||||
}
|
||||
|
||||
|
@ -819,7 +819,7 @@ Traversal:
|
|||
Each: travExpr,
|
||||
Item: itemExpr,
|
||||
|
||||
SrcRange: hcl.RangeBetween(open.Range, travExpr.Range()),
|
||||
SrcRange: hcl.RangeBetween(from.Range(), travExpr.Range()),
|
||||
MarkerRange: hcl.RangeBetween(open.Range, close.Range),
|
||||
}
|
||||
|
||||
|
@ -867,8 +867,9 @@ Traversal:
|
|||
Collection: ret,
|
||||
Key: keyExpr,
|
||||
|
||||
SrcRange: rng,
|
||||
SrcRange: hcl.RangeBetween(from.Range(), rng),
|
||||
OpenRange: open.Range,
|
||||
BracketRange: rng,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -899,7 +900,7 @@ func makeRelativeTraversal(expr Expression, next hcl.Traverser, rng hcl.Range) E
|
|||
return &RelativeTraversalExpr{
|
||||
Source: expr,
|
||||
Traversal: hcl.Traversal{next},
|
||||
SrcRange: rng,
|
||||
SrcRange: hcl.RangeBetween(expr.Range(), rng),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ func (b *Body) Attributes() map[string]*Attribute {
|
|||
// Blocks returns a new slice of all the blocks in the body.
|
||||
func (b *Body) Blocks() []*Block {
|
||||
ret := make([]*Block, 0, len(b.items))
|
||||
for n := range b.items {
|
||||
for _, n := range b.items.List() {
|
||||
if block, isBlock := n.content.(*Block); isBlock {
|
||||
ret = append(ret, block)
|
||||
}
|
||||
|
@ -134,6 +134,26 @@ func (b *Body) RemoveBlock(block *Block) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// SetAttributeRaw either replaces the expression of an existing attribute
|
||||
// of the given name or adds a new attribute definition to the end of the block,
|
||||
// using the given tokens verbatim as the expression.
|
||||
//
|
||||
// The same caveats apply to this function as for NewExpressionRaw on which
|
||||
// it is based. If possible, prefer to use SetAttributeValue or
|
||||
// SetAttributeTraversal.
|
||||
func (b *Body) SetAttributeRaw(name string, tokens Tokens) *Attribute {
|
||||
attr := b.GetAttribute(name)
|
||||
expr := NewExpressionRaw(tokens)
|
||||
if attr != nil {
|
||||
attr.expr = attr.expr.ReplaceWith(expr)
|
||||
} else {
|
||||
attr := newAttribute()
|
||||
attr.init(name, expr)
|
||||
b.appendItem(attr)
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
// SetAttributeValue either replaces the expression of an existing attribute
|
||||
// of the given name or adds a new attribute definition to the end of the block.
|
||||
//
|
||||
|
|
|
@ -21,6 +21,29 @@ func newExpression() *Expression {
|
|||
}
|
||||
}
|
||||
|
||||
// NewExpressionRaw constructs an expression containing the given raw tokens.
|
||||
//
|
||||
// There is no automatic validation that the given tokens produce a valid
|
||||
// expression. Callers of thus function must take care to produce invalid
|
||||
// expression tokens. Where possible, use the higher-level functions
|
||||
// NewExpressionLiteral or NewExpressionAbsTraversal instead.
|
||||
//
|
||||
// Because NewExpressionRaw does not interpret the given tokens in any way,
|
||||
// an expression created by NewExpressionRaw will produce an empty result
|
||||
// for calls to its method Variables, even if the given token sequence
|
||||
// contains a subslice that would normally be interpreted as a traversal under
|
||||
// parsing.
|
||||
func NewExpressionRaw(tokens Tokens) *Expression {
|
||||
expr := newExpression()
|
||||
// We copy the tokens here in order to make sure that later mutations
|
||||
// by the caller don't inadvertently cause our expression to become
|
||||
// invalid.
|
||||
copyTokens := make(Tokens, len(tokens))
|
||||
copy(copyTokens, tokens)
|
||||
expr.children.AppendUnstructuredTokens(copyTokens)
|
||||
return expr
|
||||
}
|
||||
|
||||
// NewExpressionLiteral constructs an an expression that represents the given
|
||||
// literal value.
|
||||
//
|
||||
|
|
|
@ -159,12 +159,12 @@ func appendTokensForValue(val cty.Value, toks Tokens) Tokens {
|
|||
|
||||
func appendTokensForTraversal(traversal hcl.Traversal, toks Tokens) Tokens {
|
||||
for _, step := range traversal {
|
||||
appendTokensForTraversalStep(step, toks)
|
||||
toks = appendTokensForTraversalStep(step, toks)
|
||||
}
|
||||
return toks
|
||||
}
|
||||
|
||||
func appendTokensForTraversalStep(step hcl.Traverser, toks Tokens) {
|
||||
func appendTokensForTraversalStep(step hcl.Traverser, toks Tokens) Tokens {
|
||||
switch ts := step.(type) {
|
||||
case hcl.TraverseRoot:
|
||||
toks = append(toks, &Token{
|
||||
|
@ -188,7 +188,7 @@ func appendTokensForTraversalStep(step hcl.Traverser, toks Tokens) {
|
|||
Type: hclsyntax.TokenOBrack,
|
||||
Bytes: []byte{'['},
|
||||
})
|
||||
appendTokensForValue(ts.Key, toks)
|
||||
toks = appendTokensForValue(ts.Key, toks)
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenCBrack,
|
||||
Bytes: []byte{']'},
|
||||
|
@ -196,6 +196,8 @@ func appendTokensForTraversalStep(step hcl.Traverser, toks Tokens) {
|
|||
default:
|
||||
panic(fmt.Sprintf("unsupported traversal step type %T", step))
|
||||
}
|
||||
|
||||
return toks
|
||||
}
|
||||
|
||||
func escapeQuotedStringLit(s string) []byte {
|
||||
|
|
|
@ -76,6 +76,16 @@ func Expand(path string) (string, error) {
|
|||
return filepath.Join(dir, path[1:]), nil
|
||||
}
|
||||
|
||||
// Reset clears the cache, forcing the next call to Dir to re-detect
|
||||
// the home directory. This generally never has to be called, but can be
|
||||
// useful in tests if you're modifying the home directory via the HOME
|
||||
// env var or something.
|
||||
func Reset() {
|
||||
cacheLock.Lock()
|
||||
defer cacheLock.Unlock()
|
||||
homedirCache = ""
|
||||
}
|
||||
|
||||
func dirUnix() (string, error) {
|
||||
homeEnv := "HOME"
|
||||
if runtime.GOOS == "plan9" {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.12
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# 1.0.1 (July 30, 2019)
|
||||
|
||||
* The YAML decoder is now correctly treating quoted scalars as verbatim literal
|
||||
strings rather than using the fuzzy type selection rules for them. Fuzzy
|
||||
type selection rules still apply to unquoted scalars.
|
||||
([#4](https://github.com/zclconf/go-cty-yaml/pull/4))
|
||||
|
||||
# 1.0.0 (May 26, 2019)
|
||||
|
||||
Initial release.
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
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.
|
|
@ -0,0 +1,31 @@
|
|||
The following files were ported to Go from C files of libyaml, and thus
|
||||
are still covered by their original copyright and license:
|
||||
|
||||
apic.go
|
||||
emitterc.go
|
||||
parserc.go
|
||||
readerc.go
|
||||
scannerc.go
|
||||
writerc.go
|
||||
yamlh.go
|
||||
yamlprivateh.go
|
||||
|
||||
Copyright (c) 2006 Kirill Simonov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,20 @@
|
|||
This package is derived from gopkg.in/yaml.v2, which is copyright
|
||||
2011-2016 Canonical Ltd.
|
||||
|
||||
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.
|
||||
|
||||
Includes mechanical ports of code from libyaml, distributed under its original
|
||||
license. See LICENSE.libyaml for more information.
|
||||
|
||||
Modifications for cty interfacing copyright 2019 Martin Atkins, and
|
||||
distributed under the same license terms.
|
|
@ -0,0 +1,739 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
|
||||
//fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens))
|
||||
|
||||
// Check if we can move the queue at the beginning of the buffer.
|
||||
if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) {
|
||||
if parser.tokens_head != len(parser.tokens) {
|
||||
copy(parser.tokens, parser.tokens[parser.tokens_head:])
|
||||
}
|
||||
parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head]
|
||||
parser.tokens_head = 0
|
||||
}
|
||||
parser.tokens = append(parser.tokens, *token)
|
||||
if pos < 0 {
|
||||
return
|
||||
}
|
||||
copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:])
|
||||
parser.tokens[parser.tokens_head+pos] = *token
|
||||
}
|
||||
|
||||
// Create a new parser object.
|
||||
func yaml_parser_initialize(parser *yaml_parser_t) bool {
|
||||
*parser = yaml_parser_t{
|
||||
raw_buffer: make([]byte, 0, input_raw_buffer_size),
|
||||
buffer: make([]byte, 0, input_buffer_size),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Destroy a parser object.
|
||||
func yaml_parser_delete(parser *yaml_parser_t) {
|
||||
*parser = yaml_parser_t{}
|
||||
}
|
||||
|
||||
// String read handler.
|
||||
func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
if parser.input_pos == len(parser.input) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(buffer, parser.input[parser.input_pos:])
|
||||
parser.input_pos += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Reader read handler.
|
||||
func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
return parser.input_reader.Read(buffer)
|
||||
}
|
||||
|
||||
// Set a string input.
|
||||
func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_string_read_handler
|
||||
parser.input = input
|
||||
parser.input_pos = 0
|
||||
}
|
||||
|
||||
// Set a file input.
|
||||
func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_reader_read_handler
|
||||
parser.input_reader = r
|
||||
}
|
||||
|
||||
// Set the source encoding.
|
||||
func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
|
||||
if parser.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the encoding only once")
|
||||
}
|
||||
parser.encoding = encoding
|
||||
}
|
||||
|
||||
// Create a new emitter object.
|
||||
func yaml_emitter_initialize(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{
|
||||
buffer: make([]byte, output_buffer_size),
|
||||
raw_buffer: make([]byte, 0, output_raw_buffer_size),
|
||||
states: make([]yaml_emitter_state_t, 0, initial_stack_size),
|
||||
events: make([]yaml_event_t, 0, initial_queue_size),
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an emitter object.
|
||||
func yaml_emitter_delete(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{}
|
||||
}
|
||||
|
||||
// String write handler.
|
||||
func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
*emitter.output_buffer = append(*emitter.output_buffer, buffer...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// yaml_writer_write_handler uses emitter.output_writer to write the
|
||||
// emitted text.
|
||||
func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
_, err := emitter.output_writer.Write(buffer)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a string output.
|
||||
func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_string_write_handler
|
||||
emitter.output_buffer = output_buffer
|
||||
}
|
||||
|
||||
// Set a file output.
|
||||
func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_writer_write_handler
|
||||
emitter.output_writer = w
|
||||
}
|
||||
|
||||
// Set the output encoding.
|
||||
func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) {
|
||||
if emitter.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the output encoding only once")
|
||||
}
|
||||
emitter.encoding = encoding
|
||||
}
|
||||
|
||||
// Set the canonical output style.
|
||||
func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
|
||||
emitter.canonical = canonical
|
||||
}
|
||||
|
||||
//// Set the indentation increment.
|
||||
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
|
||||
if indent < 2 || indent > 9 {
|
||||
indent = 2
|
||||
}
|
||||
emitter.best_indent = indent
|
||||
}
|
||||
|
||||
// Set the preferred line width.
|
||||
func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) {
|
||||
if width < 0 {
|
||||
width = -1
|
||||
}
|
||||
emitter.best_width = width
|
||||
}
|
||||
|
||||
// Set if unescaped non-ASCII characters are allowed.
|
||||
func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) {
|
||||
emitter.unicode = unicode
|
||||
}
|
||||
|
||||
// Set the preferred line break character.
|
||||
func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) {
|
||||
emitter.line_break = line_break
|
||||
}
|
||||
|
||||
///*
|
||||
// * Destroy a token object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_token_delete(yaml_token_t *token)
|
||||
//{
|
||||
// assert(token); // Non-NULL token object expected.
|
||||
//
|
||||
// switch (token.type)
|
||||
// {
|
||||
// case YAML_TAG_DIRECTIVE_TOKEN:
|
||||
// yaml_free(token.data.tag_directive.handle);
|
||||
// yaml_free(token.data.tag_directive.prefix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ALIAS_TOKEN:
|
||||
// yaml_free(token.data.alias.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ANCHOR_TOKEN:
|
||||
// yaml_free(token.data.anchor.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_TAG_TOKEN:
|
||||
// yaml_free(token.data.tag.handle);
|
||||
// yaml_free(token.data.tag.suffix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_SCALAR_TOKEN:
|
||||
// yaml_free(token.data.scalar.value);
|
||||
// break;
|
||||
//
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// memset(token, 0, sizeof(yaml_token_t));
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Check if a string is a valid UTF-8 sequence.
|
||||
// *
|
||||
// * Check 'reader.c' for more details on UTF-8 encoding.
|
||||
// */
|
||||
//
|
||||
//static int
|
||||
//yaml_check_utf8(yaml_char_t *start, size_t length)
|
||||
//{
|
||||
// yaml_char_t *end = start+length;
|
||||
// yaml_char_t *pointer = start;
|
||||
//
|
||||
// while (pointer < end) {
|
||||
// unsigned char octet;
|
||||
// unsigned int width;
|
||||
// unsigned int value;
|
||||
// size_t k;
|
||||
//
|
||||
// octet = pointer[0];
|
||||
// width = (octet & 0x80) == 0x00 ? 1 :
|
||||
// (octet & 0xE0) == 0xC0 ? 2 :
|
||||
// (octet & 0xF0) == 0xE0 ? 3 :
|
||||
// (octet & 0xF8) == 0xF0 ? 4 : 0;
|
||||
// value = (octet & 0x80) == 0x00 ? octet & 0x7F :
|
||||
// (octet & 0xE0) == 0xC0 ? octet & 0x1F :
|
||||
// (octet & 0xF0) == 0xE0 ? octet & 0x0F :
|
||||
// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0;
|
||||
// if (!width) return 0;
|
||||
// if (pointer+width > end) return 0;
|
||||
// for (k = 1; k < width; k ++) {
|
||||
// octet = pointer[k];
|
||||
// if ((octet & 0xC0) != 0x80) return 0;
|
||||
// value = (value << 6) + (octet & 0x3F);
|
||||
// }
|
||||
// if (!((width == 1) ||
|
||||
// (width == 2 && value >= 0x80) ||
|
||||
// (width == 3 && value >= 0x800) ||
|
||||
// (width == 4 && value >= 0x10000))) return 0;
|
||||
//
|
||||
// pointer += width;
|
||||
// }
|
||||
//
|
||||
// return 1;
|
||||
//}
|
||||
//
|
||||
|
||||
// Create STREAM-START.
|
||||
func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_START_EVENT,
|
||||
encoding: encoding,
|
||||
}
|
||||
}
|
||||
|
||||
// Create STREAM-END.
|
||||
func yaml_stream_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-START.
|
||||
func yaml_document_start_event_initialize(
|
||||
event *yaml_event_t,
|
||||
version_directive *yaml_version_directive_t,
|
||||
tag_directives []yaml_tag_directive_t,
|
||||
implicit bool,
|
||||
) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_START_EVENT,
|
||||
version_directive: version_directive,
|
||||
tag_directives: tag_directives,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-END.
|
||||
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_END_EVENT,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
///*
|
||||
// * Create ALIAS.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t)
|
||||
//{
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// anchor_copy *yaml_char_t = NULL
|
||||
//
|
||||
// assert(event) // Non-NULL event object is expected.
|
||||
// assert(anchor) // Non-NULL anchor is expected.
|
||||
//
|
||||
// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0
|
||||
//
|
||||
// anchor_copy = yaml_strdup(anchor)
|
||||
// if (!anchor_copy)
|
||||
// return 0
|
||||
//
|
||||
// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark)
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
|
||||
// Create SCALAR.
|
||||
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SCALAR_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
value: value,
|
||||
implicit: plain_implicit,
|
||||
quoted_implicit: quoted_implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-START.
|
||||
func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-END.
|
||||
func yaml_sequence_end_event_initialize(event *yaml_event_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_END_EVENT,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create MAPPING-START.
|
||||
func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
}
|
||||
|
||||
// Create MAPPING-END.
|
||||
func yaml_mapping_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an event object.
|
||||
func yaml_event_delete(event *yaml_event_t) {
|
||||
*event = yaml_event_t{}
|
||||
}
|
||||
|
||||
///*
|
||||
// * Create a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_initialize(document *yaml_document_t,
|
||||
// version_directive *yaml_version_directive_t,
|
||||
// tag_directives_start *yaml_tag_directive_t,
|
||||
// tag_directives_end *yaml_tag_directive_t,
|
||||
// start_implicit int, end_implicit int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// struct {
|
||||
// start *yaml_node_t
|
||||
// end *yaml_node_t
|
||||
// top *yaml_node_t
|
||||
// } nodes = { NULL, NULL, NULL }
|
||||
// version_directive_copy *yaml_version_directive_t = NULL
|
||||
// struct {
|
||||
// start *yaml_tag_directive_t
|
||||
// end *yaml_tag_directive_t
|
||||
// top *yaml_tag_directive_t
|
||||
// } tag_directives_copy = { NULL, NULL, NULL }
|
||||
// value yaml_tag_directive_t = { NULL, NULL }
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert((tag_directives_start && tag_directives_end) ||
|
||||
// (tag_directives_start == tag_directives_end))
|
||||
// // Valid tag directives are expected.
|
||||
//
|
||||
// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// if (version_directive) {
|
||||
// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t))
|
||||
// if (!version_directive_copy) goto error
|
||||
// version_directive_copy.major = version_directive.major
|
||||
// version_directive_copy.minor = version_directive.minor
|
||||
// }
|
||||
//
|
||||
// if (tag_directives_start != tag_directives_end) {
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE))
|
||||
// goto error
|
||||
// for (tag_directive = tag_directives_start
|
||||
// tag_directive != tag_directives_end; tag_directive ++) {
|
||||
// assert(tag_directive.handle)
|
||||
// assert(tag_directive.prefix)
|
||||
// if (!yaml_check_utf8(tag_directive.handle,
|
||||
// strlen((char *)tag_directive.handle)))
|
||||
// goto error
|
||||
// if (!yaml_check_utf8(tag_directive.prefix,
|
||||
// strlen((char *)tag_directive.prefix)))
|
||||
// goto error
|
||||
// value.handle = yaml_strdup(tag_directive.handle)
|
||||
// value.prefix = yaml_strdup(tag_directive.prefix)
|
||||
// if (!value.handle || !value.prefix) goto error
|
||||
// if (!PUSH(&context, tag_directives_copy, value))
|
||||
// goto error
|
||||
// value.handle = NULL
|
||||
// value.prefix = NULL
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy,
|
||||
// tag_directives_copy.start, tag_directives_copy.top,
|
||||
// start_implicit, end_implicit, mark, mark)
|
||||
//
|
||||
// return 1
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, nodes)
|
||||
// yaml_free(version_directive_copy)
|
||||
// while (!STACK_EMPTY(&context, tag_directives_copy)) {
|
||||
// value yaml_tag_directive_t = POP(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
// }
|
||||
// STACK_DEL(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Destroy a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_document_delete(document *yaml_document_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
//
|
||||
// context.error = YAML_NO_ERROR // Eliminate a compiler warning.
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// while (!STACK_EMPTY(&context, document.nodes)) {
|
||||
// node yaml_node_t = POP(&context, document.nodes)
|
||||
// yaml_free(node.tag)
|
||||
// switch (node.type) {
|
||||
// case YAML_SCALAR_NODE:
|
||||
// yaml_free(node.data.scalar.value)
|
||||
// break
|
||||
// case YAML_SEQUENCE_NODE:
|
||||
// STACK_DEL(&context, node.data.sequence.items)
|
||||
// break
|
||||
// case YAML_MAPPING_NODE:
|
||||
// STACK_DEL(&context, node.data.mapping.pairs)
|
||||
// break
|
||||
// default:
|
||||
// assert(0) // Should not happen.
|
||||
// }
|
||||
// }
|
||||
// STACK_DEL(&context, document.nodes)
|
||||
//
|
||||
// yaml_free(document.version_directive)
|
||||
// for (tag_directive = document.tag_directives.start
|
||||
// tag_directive != document.tag_directives.end
|
||||
// tag_directive++) {
|
||||
// yaml_free(tag_directive.handle)
|
||||
// yaml_free(tag_directive.prefix)
|
||||
// }
|
||||
// yaml_free(document.tag_directives.start)
|
||||
//
|
||||
// memset(document, 0, sizeof(yaml_document_t))
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get a document node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_node(document *yaml_document_t, index int)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (index > 0 && document.nodes.start + index <= document.nodes.top) {
|
||||
// return document.nodes.start + index - 1
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get the root object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_root_node(document *yaml_document_t)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (document.nodes.top != document.nodes.start) {
|
||||
// return document.nodes.start
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a scalar node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_scalar(document *yaml_document_t,
|
||||
// tag *yaml_char_t, value *yaml_char_t, length int,
|
||||
// style yaml_scalar_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// value_copy *yaml_char_t = NULL
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert(value) // Non-NULL value is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (length < 0) {
|
||||
// length = strlen((char *)value)
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(value, length)) goto error
|
||||
// value_copy = yaml_malloc(length+1)
|
||||
// if (!value_copy) goto error
|
||||
// memcpy(value_copy, value, length)
|
||||
// value_copy[length] = '\0'
|
||||
//
|
||||
// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// yaml_free(tag_copy)
|
||||
// yaml_free(value_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a sequence node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_sequence(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_sequence_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_item_t
|
||||
// end *yaml_node_item_t
|
||||
// top *yaml_node_item_t
|
||||
// } items = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, items)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a mapping node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_mapping(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_mapping_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_pair_t
|
||||
// end *yaml_node_pair_t
|
||||
// top *yaml_node_pair_t
|
||||
// } pairs = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, pairs)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append an item to a sequence node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_sequence_item(document *yaml_document_t,
|
||||
// sequence int, item int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(sequence > 0
|
||||
// && document.nodes.start + sequence <= document.nodes.top)
|
||||
// // Valid sequence id is required.
|
||||
// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE)
|
||||
// // A sequence node is required.
|
||||
// assert(item > 0 && document.nodes.start + item <= document.nodes.top)
|
||||
// // Valid item id is required.
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[sequence-1].data.sequence.items, item))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append a pair of a key and a value to a mapping node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_mapping_pair(document *yaml_document_t,
|
||||
// mapping int, key int, value int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// pair yaml_node_pair_t
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(mapping > 0
|
||||
// && document.nodes.start + mapping <= document.nodes.top)
|
||||
// // Valid mapping id is required.
|
||||
// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE)
|
||||
// // A mapping node is required.
|
||||
// assert(key > 0 && document.nodes.start + key <= document.nodes.top)
|
||||
// // Valid key id is required.
|
||||
// assert(value > 0 && document.nodes.start + value <= document.nodes.top)
|
||||
// // Valid value id is required.
|
||||
//
|
||||
// pair.key = key
|
||||
// pair.value = value
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[mapping-1].data.mapping.pairs, pair))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
//
|
|
@ -0,0 +1,69 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// ConverterConfig is used to configure a new converter, using NewConverter.
|
||||
type ConverterConfig struct {
|
||||
// EncodeAsFlow, when set to true, causes Marshal to produce flow-style
|
||||
// mapping and sequence serializations.
|
||||
EncodeAsFlow bool
|
||||
}
|
||||
|
||||
// A Converter can marshal and unmarshal between cty values and YAML bytes.
|
||||
//
|
||||
// Because there are many different ways to map cty to YAML and vice-versa,
|
||||
// a converter is configurable using the settings in ConverterConfig, which
|
||||
// allow for a few different permutations of mapping to YAML.
|
||||
//
|
||||
// If you are just trying to work with generic, standard YAML, the predefined
|
||||
// converter in Standard should be good enough.
|
||||
type Converter struct {
|
||||
encodeAsFlow bool
|
||||
}
|
||||
|
||||
// NewConverter creates a new Converter with the given configuration.
|
||||
func NewConverter(config *ConverterConfig) *Converter {
|
||||
return &Converter{
|
||||
encodeAsFlow: config.EncodeAsFlow,
|
||||
}
|
||||
}
|
||||
|
||||
// Standard is a predefined Converter that produces and consumes generic YAML
|
||||
// using only built-in constructs that any other YAML implementation ought to
|
||||
// understand.
|
||||
var Standard *Converter = NewConverter(&ConverterConfig{})
|
||||
|
||||
// ImpliedType analyzes the given source code and returns a suitable type that
|
||||
// it could be decoded into.
|
||||
//
|
||||
// For a converter that is using standard YAML rather than cty-specific custom
|
||||
// tags, only a subset of cty types can be produced: strings, numbers, bools,
|
||||
// tuple types, and object types.
|
||||
func (c *Converter) ImpliedType(src []byte) (cty.Type, error) {
|
||||
return c.impliedType(src)
|
||||
}
|
||||
|
||||
// Marshal serializes the given value into a YAML document, using a fixed
|
||||
// mapping from cty types to YAML constructs.
|
||||
//
|
||||
// Note that unlike the function of the same name in the cty JSON package,
|
||||
// this does not take a type constraint and therefore the YAML serialization
|
||||
// cannot preserve late-bound type information in the serialization to be
|
||||
// recovered from Unmarshal. Instead, any cty.DynamicPseudoType in the type
|
||||
// constraint given to Unmarshal will be decoded as if the corresponding portion
|
||||
// of the input were processed with ImpliedType to find a target type.
|
||||
func (c *Converter) Marshal(v cty.Value) ([]byte, error) {
|
||||
return c.marshal(v)
|
||||
}
|
||||
|
||||
// Unmarshal reads the document found within the given source buffer
|
||||
// and attempts to convert it into a value conforming to the given type
|
||||
// constraint.
|
||||
//
|
||||
// An error is returned if the given source contains any YAML document
|
||||
// delimiters.
|
||||
func (c *Converter) Unmarshal(src []byte, ty cty.Type) (cty.Value, error) {
|
||||
return c.unmarshal(src, ty)
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// YAMLDecodeFunc is a cty function for decoding arbitrary YAML source code
|
||||
// into a cty Value, using the ImpliedType and Unmarshal methods of the
|
||||
// Standard pre-defined converter.
|
||||
var YAMLDecodeFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "src",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: func(args []cty.Value) (cty.Type, error) {
|
||||
if !args[0].IsKnown() {
|
||||
return cty.DynamicPseudoType, nil
|
||||
}
|
||||
if args[0].IsNull() {
|
||||
return cty.NilType, function.NewArgErrorf(0, "YAML source code cannot be null")
|
||||
}
|
||||
return Standard.ImpliedType([]byte(args[0].AsString()))
|
||||
},
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
if retType == cty.DynamicPseudoType {
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
return Standard.Unmarshal([]byte(args[0].AsString()), retType)
|
||||
},
|
||||
})
|
||||
|
||||
// YAMLEncodeFunc is a cty function for encoding an arbitrary cty value
|
||||
// into YAML.
|
||||
var YAMLEncodeFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "value",
|
||||
Type: cty.DynamicPseudoType,
|
||||
AllowNull: true,
|
||||
AllowDynamicType: true,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
if !args[0].IsWhollyKnown() {
|
||||
return cty.UnknownVal(retType), nil
|
||||
}
|
||||
raw, err := Standard.Marshal(args[0])
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
return cty.StringVal(string(raw)), nil
|
||||
},
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue