Extend hcl2 support with more functions

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2020-12-23 12:33:55 +01:00
parent 74f76cf4e9
commit 96e7f3224a
90 changed files with 4425 additions and 198 deletions

354
vendor/github.com/hashicorp/go-cty-funcs/LICENSE generated vendored Normal file
View File

@ -0,0 +1,354 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributors Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third partys
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
partys negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a partys ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.

49
vendor/github.com/hashicorp/go-cty-funcs/cidr/host.go generated vendored Normal file
View File

@ -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})
}

View File

@ -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})
}

View File

@ -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})
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -0,0 +1,27 @@
package crypto
import (
"hash"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
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
},
})
}

18
vendor/github.com/hashicorp/go-cty-funcs/crypto/md5.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
package crypto
import (
"crypto/md5"
"encoding/hex"
"github.com/zclconf/go-cty/cty"
)
// 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)
// 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})
}

64
vendor/github.com/hashicorp/go-cty-funcs/crypto/rsa.go generated vendored Normal file
View File

@ -0,0 +1,64 @@
package crypto
import (
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
// 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
},
})
// 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})
}

40
vendor/github.com/hashicorp/go-cty-funcs/crypto/sha.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
package crypto
import (
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"github.com/zclconf/go-cty/cty"
)
// 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)
// 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)
// 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)
// 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})
}

View File

@ -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})
}

View File

@ -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})
}

View File

@ -0,0 +1,28 @@
package uuid
import (
"github.com/google/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) {
uuid, err := uuid.NewRandom()
if err != nil {
return cty.UnknownVal(cty.String), err
}
return cty.StringVal(uuid.String()), 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)
}

View File

@ -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})
}

View File

@ -1,5 +1,52 @@
# HCL Changelog
## v2.8.1 (Unreleased)
### Bugs Fixed
* hclsyntax: Fix panic when expanding marked function arguments. ([#429](https://github.com/hashicorp/hcl/pull/429))
* hclsyntax: Error when attempting to use a marked value as an object key. ([#434](https://github.com/hashicorp/hcl/pull/434))
* hclsyntax: Error when attempting to use a marked value as an object key in expressions. ([#433](https://github.com/hashicorp/hcl/pull/433))
## v2.8.0 (December 7, 2020)
### Enhancements
* hclsyntax: Expression grouping parentheses will now be reflected by an explicit node in the AST, whereas before they were only considered during parsing. ([#426](https://github.com/hashicorp/hcl/pull/426))
### Bugs Fixed
* hclwrite: The parser will now correctly include the `(` and `)` tokens when an expression is surrounded by parentheses. Previously it would incorrectly recognize those tokens as being extraneous tokens outside of the expression. ([#426](https://github.com/hashicorp/hcl/pull/426))
* hclwrite: The formatter will now remove (rather than insert) spaces between the `!` (unary boolean "not") operator and its subsequent operand. ([#403](https://github.com/hashicorp/hcl/pull/403))
* hclsyntax: Unmark conditional values in expressions before checking their truthfulness ([#427](https://github.com/hashicorp/hcl/pull/427))
## v2.7.2 (November 30, 2020)
### Bugs Fixed
* gohcl: Fix panic when decoding into type containing value slices. ([#335](https://github.com/hashicorp/hcl/pull/335))
* hclsyntax: The unusual expression `null[*]` was previously always returning an unknown value, even though the rules for `[*]` normally call for it to return an empty tuple when applied to a null. As well as being a surprising result, it was particularly problematic because it violated the rule that a calling application may assume that an expression result will always be known unless the application itself introduces unknown values via the evaluation context. `null[*]` will now produce an empty tuple. ([#416](https://github.com/hashicorp/hcl/pull/416))
* hclsyntax: Fix panic when traversing a list, tuple, or map with cty "marks" ([#424](https://github.com/hashicorp/hcl/pull/424))
## v2.7.1 (November 18, 2020)
### Bugs Fixed
* hclwrite: Correctly handle blank quoted string block labels, instead of dropping them ([#422](https://github.com/hashicorp/hcl/pull/422))
## v2.7.0 (October 14, 2020)
### Enhancements
* json: There is a new function `ParseWithStartPos`, which allows overriding the starting position for parsing in case the given JSON bytes are a fragment of a larger document, such as might happen when decoding with `encoding/json` into a `json.RawMessage`. ([#389](https://github.com/hashicorp/hcl/pull/389))
* json: There is a new function `ParseExpression`, which allows parsing a JSON string directly in expression mode, whereas previously it was only possible to parse a JSON string in body mode. ([#381](https://github.com/hashicorp/hcl/pull/381))
* hclwrite: `Block` type now supports `SetType` and `SetLabels`, allowing surgical changes to the type and labels of an existing block without having to reconstruct the entire block. ([#340](https://github.com/hashicorp/hcl/pull/340))
### Bugs Fixed
* hclsyntax: Fix confusing error message for bitwise OR operator ([#380](https://github.com/hashicorp/hcl/pull/380))
* hclsyntax: Several bug fixes for using HCL with values containing cty "marks" ([#404](https://github.com/hashicorp/hcl/pull/404), [#406](https://github.com/hashicorp/hcl/pull/404), [#407](https://github.com/hashicorp/hcl/pull/404))
## v2.6.0 (June 4, 2020)
### Enhancements

View File

@ -33,11 +33,25 @@ package main
import (
"log"
"github.com/hashicorp/hcl/v2/hclsimple"
)
type Config struct {
LogLevel string `hcl:"log_level"`
IOMode string `hcl:"io_mode"`
Service ServiceConfig `hcl:"service,block"`
}
type ServiceConfig struct {
Protocol string `hcl:"protocol,label"`
Type string `hcl:"type,label"`
ListenAddr string `hcl:"listen_addr"`
Processes []ProcessConfig `hcl:"process,block"`
}
type ProcessConfig struct {
Type string `hcl:"type,label"`
Command []string `hcl:"command"`
}
func main() {

View File

@ -1,13 +0,0 @@
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 ./...

View File

@ -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,
},
}
```

View File

@ -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
}

View File

@ -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.

11
vendor/github.com/hashicorp/hcl/v2/ext/typeexpr/doc.go generated vendored Normal file
View File

@ -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

View File

@ -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(),
}}
}
}

View File

@ -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))
}

View File

@ -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
},
})
}

View File

@ -25,4 +25,4 @@ inclusion in a `hcl.EvalContext`. It also returns a new `cty.Body` that
contains the remainder of the content from the given body, allowing for
further processing of remaining content.
For more information, see [the godoc reference](http://godoc.org/github.com/hashicorp/hcl/v2/ext/userfunc).
For more information, see [the godoc reference](https://pkg.go.dev/github.com/hashicorp/hcl/v2/ext/userfunc?tab=doc).

View File

@ -65,6 +65,19 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
tags := getFieldTags(val.Type())
if tags.Body != nil {
fieldIdx := *tags.Body
field := val.Type().Field(fieldIdx)
fieldV := val.Field(fieldIdx)
switch {
case bodyType.AssignableTo(field.Type):
fieldV.Set(reflect.ValueOf(body))
default:
diags = append(diags, decodeBodyToValue(body, ctx, fieldV)...)
}
}
if tags.Remain != nil {
fieldIdx := *tags.Remain
field := val.Type().Field(fieldIdx)
@ -185,6 +198,9 @@ func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value)
diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
sli.Index(i).Set(v)
} else {
if i >= sli.Len() {
sli = reflect.Append(sli, reflect.Indirect(reflect.New(ty)))
}
diags = append(diags, decodeBlockToValue(block, ctx, sli.Index(i))...)
}
}

View File

@ -30,6 +30,13 @@
// in which case multiple blocks of the corresponding type are decoded into
// the slice.
//
// "body" can be placed on a single field of type hcl.Body to capture
// the full hcl.Body that was decoded for a block. This does not allow leftover
// values like "remain", so a decoding error will still be returned if leftover
// fields are given. If you want to capture the decoding body PLUS leftover
// fields, you must specify a "remain" field as well to prevent errors. The
// body field and the remain field will both contain the leftover fields.
//
// "label" fields are considered only in a struct used as the type of a field
// marked as "block", and are used sequentially to capture the labels of
// the blocks being decoded. In this case, the name token is used only as

View File

@ -113,6 +113,7 @@ type fieldTags struct {
Blocks map[string]int
Labels []labelField
Remain *int
Body *int
Optional map[string]bool
}
@ -162,6 +163,12 @@ func getFieldTags(ty reflect.Type) *fieldTags {
}
idx := i // copy, because this loop will continue assigning to i
ret.Remain = &idx
case "body":
if ret.Body != nil {
panic("only one 'body' tag is permitted")
}
idx := i // copy, because this loop will continue assigning to i
ret.Body = &idx
case "optional":
ret.Attributes[name] = i
ret.Optional[name] = true

View File

@ -27,6 +27,32 @@ type Expression interface {
// Assert that Expression implements hcl.Expression
var assertExprImplExpr hcl.Expression = Expression(nil)
// ParenthesesExpr represents an expression written in grouping
// parentheses.
//
// The parser takes care of the precedence effect of the parentheses, so the
// only purpose of this separate expression node is to capture the source range
// of the parentheses themselves, rather than the source range of the
// expression within. All of the other expression operations just pass through
// to the underlying expression.
type ParenthesesExpr struct {
Expression
SrcRange hcl.Range
}
var _ hcl.Expression = (*ParenthesesExpr)(nil)
func (e *ParenthesesExpr) Range() hcl.Range {
return e.SrcRange
}
func (e *ParenthesesExpr) walkChildNodes(w internalWalkFunc) {
// We override the walkChildNodes from the embedded Expression to
// ensure that both the parentheses _and_ the content are visible
// in a walk.
w(e.Expression)
}
// LiteralValueExpr is an expression that just always returns a given value.
type LiteralValueExpr struct {
Val cty.Value
@ -291,13 +317,17 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
return cty.DynamicVal, diags
}
// When expanding arguments from a collection, we must first unmark
// the collection itself, and apply any marks directly to the
// elements. This ensures that marks propagate correctly.
expandVal, marks := expandVal.Unmark()
newArgs := make([]Expression, 0, (len(args)-1)+expandVal.LengthInt())
newArgs = append(newArgs, args[:len(args)-1]...)
it := expandVal.ElementIterator()
for it.Next() {
_, val := it.Element()
newArgs = append(newArgs, &LiteralValueExpr{
Val: val,
Val: val.WithMarks(marks),
SrcRange: expandExpr.Range(),
})
}
@ -598,6 +628,8 @@ func (e *ConditionalExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostic
return cty.UnknownVal(resultType), diags
}
// Unmark result before testing for truthiness
condResult, _ = condResult.UnmarkDeep()
if condResult.True() {
diags = append(diags, trueDiags...)
if convs[0] != nil {
@ -793,6 +825,19 @@ func (e *ObjectConsExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics
continue
}
if key.IsMarked() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Marked value as key",
Detail: "Can't use a marked value as a key.",
Subject: item.ValueExpr.Range().Ptr(),
Expression: item.KeyExpr,
EvalContext: ctx,
})
known = false
continue
}
var err error
key, err = convert.Convert(key, cty.String)
if err != nil {
@ -971,6 +1016,9 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
if collVal.Type() == cty.DynamicPseudoType {
return cty.DynamicVal, diags
}
// Unmark collection before checking for iterability, because marked
// values cannot be iterated
collVal, marks := collVal.Unmark()
if !collVal.CanIterateElements() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
@ -1140,6 +1188,19 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
continue
}
if key.IsMarked() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid object key",
Detail: "Marked values cannot be used as object keys.",
Subject: e.KeyExpr.Range().Ptr(),
Context: &e.SrcRange,
Expression: e.KeyExpr,
EvalContext: childCtx,
})
continue
}
val, valDiags := e.ValExpr.Value(childCtx)
diags = append(diags, valDiags...)
@ -1178,7 +1239,7 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
}
}
return cty.ObjectVal(vals), diags
return cty.ObjectVal(vals).WithMarks(marks), diags
} else {
// Producing a tuple
@ -1254,7 +1315,7 @@ func (e *ForExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
return cty.DynamicVal, diags
}
return cty.TupleVal(vals), diags
return cty.TupleVal(vals).WithMarks(marks), diags
}
}
@ -1317,12 +1378,6 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
}
sourceTy := sourceVal.Type()
if sourceTy == cty.DynamicPseudoType {
// If we don't even know the _type_ of our source value yet then
// we'll need to defer all processing, since we can't decide our
// result type either.
return cty.DynamicVal, diags
}
// A "special power" of splat expressions is that they can be applied
// both to tuples/lists and to other values, and in the latter case
@ -1346,6 +1401,13 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
return cty.DynamicVal, diags
}
if sourceTy == cty.DynamicPseudoType {
// If we don't even know the _type_ of our source value yet then
// we'll need to defer all processing, since we can't decide our
// result type either.
return cty.DynamicVal, diags
}
if autoUpgrade {
sourceVal = cty.TupleVal([]cty.Value{sourceVal})
sourceTy = sourceVal.Type()

View File

@ -26,6 +26,9 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
var diags hcl.Diagnostics
isKnown := true
// Maintain a set of marks for values used in the template
marks := make(cty.ValueMarks)
for _, part := range e.Parts {
partVal, partDiags := part.Value(ctx)
diags = append(diags, partDiags...)
@ -71,14 +74,24 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
continue
}
buf.WriteString(strVal.AsString())
// Unmark the part and merge its marks into the set
unmarked, partMarks := strVal.Unmark()
for k, v := range partMarks {
marks[k] = v
}
buf.WriteString(unmarked.AsString())
}
var ret cty.Value
if !isKnown {
return cty.UnknownVal(cty.String), diags
ret = cty.UnknownVal(cty.String)
} else {
ret = cty.StringVal(buf.String())
}
return cty.StringVal(buf.String()), diags
// Apply the full set of marks to the returned value
return ret.WithMarks(marks), diags
}
func (e *TemplateExpr) Range() hcl.Range {

View File

@ -911,7 +911,7 @@ func (p *parser) parseExpressionTerm() (Expression, hcl.Diagnostics) {
switch start.Type {
case TokenOParen:
p.Read() // eat open paren
oParen := p.Read() // eat open paren
p.PushIncludeNewlines(false)
@ -937,9 +937,19 @@ func (p *parser) parseExpressionTerm() (Expression, hcl.Diagnostics) {
p.setRecovery()
}
p.Read() // eat closing paren
cParen := p.Read() // eat closing paren
p.PopIncludeNewlines()
// Our parser's already taken care of the precedence effect of the
// parentheses by considering them to be a kind of "term", but we
// still need to include the parentheses in our AST so we can give
// an accurate representation of the source range that includes the
// open and closing parentheses.
expr = &ParenthesesExpr{
Expression: expr,
SrcRange: hcl.RangeBetween(oParen.Range, cParen.Range),
}
return expr, diags
case TokenNumberLit:

View File

@ -202,7 +202,7 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
case TokenBitwiseAnd:
suggestion = " Did you mean boolean AND (\"&&\")?"
case TokenBitwiseOr:
suggestion = " Did you mean boolean OR (\"&&\")?"
suggestion = " Did you mean boolean OR (\"||\")?"
case TokenBitwiseNot:
suggestion = " Did you mean boolean NOT (\"!\")?"
}
@ -294,12 +294,23 @@ func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
Subject: &tok.Range,
})
case TokenInvalid:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid character",
Detail: "This character is not used within the language.",
Subject: &tok.Range,
})
chars := string(tok.Bytes)
switch chars {
case "“", "”":
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid character",
Detail: "\"Curly quotes\" are not valid here. These can sometimes be inadvertently introduced when sharing code via documents or discussion forums. It might help to replace the character with a \"straight quote\".",
Subject: &tok.Range,
})
default:
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid character",
Detail: "This character is not used within the language.",
Subject: &tok.Range,
})
}
}
}
return diags

View File

@ -10,7 +10,7 @@ type Block struct {
leadComments *node
typeName *node
labels nodeSet
labels *node
open *node
body *node
close *node
@ -19,7 +19,6 @@ type Block struct {
func newBlock() *Block {
return &Block{
inTree: newInTree(),
labels: newNodeSet(),
}
}
@ -35,12 +34,8 @@ func (b *Block) init(typeName string, labels []string) {
nameObj := newIdentifier(nameTok)
b.leadComments = b.children.Append(newComments(nil))
b.typeName = b.children.Append(nameObj)
for _, label := range labels {
labelToks := TokensForValue(cty.StringVal(label))
labelObj := newQuoted(labelToks)
labelNode := b.children.Append(labelObj)
b.labels.Add(labelNode)
}
labelsObj := newBlockLabels(labels)
b.labels = b.children.Append(labelsObj)
b.open = b.children.AppendUnstructuredTokens(Tokens{
{
Type: hclsyntax.TokenOBrace,
@ -79,10 +74,68 @@ func (b *Block) Type() string {
return string(typeNameObj.token.Bytes)
}
// SetType updates the type name of the block to a given name.
func (b *Block) SetType(typeName string) {
nameTok := newIdentToken(typeName)
nameObj := newIdentifier(nameTok)
b.typeName.ReplaceWith(nameObj)
}
// Labels returns the labels of the block.
func (b *Block) Labels() []string {
labelNames := make([]string, 0, len(b.labels))
list := b.labels.List()
return b.labelsObj().Current()
}
// SetLabels updates the labels of the block to given labels.
// Since we cannot assume that old and new labels are equal in length,
// remove old labels and insert new ones before TokenOBrace.
func (b *Block) SetLabels(labels []string) {
b.labelsObj().Replace(labels)
}
// labelsObj returns the internal node content representation of the block
// labels. This is not part of the public API because we're intentionally
// exposing only a limited API to get/set labels on the block itself in a
// manner similar to the main hcl.Block type, but our block accessors all
// use this to get the underlying node content to work with.
func (b *Block) labelsObj() *blockLabels {
return b.labels.content.(*blockLabels)
}
type blockLabels struct {
inTree
items nodeSet
}
func newBlockLabels(labels []string) *blockLabels {
ret := &blockLabels{
inTree: newInTree(),
items: newNodeSet(),
}
ret.Replace(labels)
return ret
}
func (bl *blockLabels) Replace(newLabels []string) {
bl.inTree.children.Clear()
bl.items.Clear()
for _, label := range newLabels {
labelToks := TokensForValue(cty.StringVal(label))
// Force a new label to use the quoted form, which is the idiomatic
// form. The unquoted form is supported in HCL 2 only for compatibility
// with historical use in HCL 1.
labelObj := newQuoted(labelToks)
labelNode := bl.children.Append(labelObj)
bl.items.Add(labelNode)
}
}
func (bl *blockLabels) Current() []string {
labelNames := make([]string, 0, len(bl.items))
list := bl.items.List()
for _, label := range list {
switch labelObj := label.content.(type) {
@ -106,6 +159,12 @@ func (b *Block) Labels() []string {
if !diags.HasErrors() {
labelNames = append(labelNames, labelString)
}
} else if len(tokens) == 2 &&
tokens[0].Type == hclsyntax.TokenOQuote &&
tokens[1].Type == hclsyntax.TokenCQuote {
// An open quote followed immediately by a closing quote is a
// valid but unusual blank string label.
labelNames = append(labelNames, "")
}
default:

View File

@ -263,6 +263,10 @@ func spaceAfterToken(subject, before, after *Token) bool {
case after.Type == hclsyntax.TokenOBrack && (subject.Type == hclsyntax.TokenIdent || subject.Type == hclsyntax.TokenNumberLit || tokenBracketChange(subject) < 0):
return false
case subject.Type == hclsyntax.TokenBang:
// No space after a bang
return false
case subject.Type == hclsyntax.TokenMinus:
// Since a minus can either be subtraction or negation, and the latter
// should _not_ have a space after it, we need to use some heuristics

View File

@ -130,6 +130,36 @@ func (ns *nodes) AppendNode(n *node) {
}
}
// Insert inserts a nodeContent at a given position.
// This is just a wrapper for InsertNode. See InsertNode for details.
func (ns *nodes) Insert(pos *node, c nodeContent) *node {
n := &node{
content: c,
}
ns.InsertNode(pos, n)
n.list = ns
return n
}
// InsertNode inserts a node at a given position.
// The first argument is a node reference before which to insert.
// To insert it to an empty list, set position to nil.
func (ns *nodes) InsertNode(pos *node, n *node) {
if pos == nil {
// inserts n to empty list.
ns.first = n
ns.last = n
} else {
// inserts n before pos.
pos.before.after = n
n.before = pos.before
pos.before = n
n.after = pos
}
n.list = ns
}
func (ns *nodes) AppendUnstructuredTokens(tokens Tokens) *node {
if len(tokens) == 0 {
return nil
@ -177,6 +207,12 @@ func (ns nodeSet) Remove(n *node) {
delete(ns, n)
}
func (ns nodeSet) Clear() {
for n := range ns {
delete(ns, n)
}
}
func (ns nodeSet) List() []*node {
if len(ns) == 0 {
return nil

View File

@ -289,7 +289,6 @@ func parseAttribute(nativeAttr *hclsyntax.Attribute, from, leadComments, lineCom
func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments, newline inputTokens) *node {
block := &Block{
inTree: newInTree(),
labels: newNodeSet(),
}
children := block.inTree.children
@ -312,24 +311,13 @@ func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments,
children.AppendNode(in)
}
for _, rng := range nativeBlock.LabelRanges {
var labelTokens inputTokens
before, labelTokens, from = from.Partition(rng)
children.AppendUnstructuredTokens(before.Tokens())
tokens := labelTokens.Tokens()
var ln *node
if len(tokens) == 1 && tokens[0].Type == hclsyntax.TokenIdent {
ln = newNode(newIdentifier(tokens[0]))
} else {
ln = newNode(newQuoted(tokens))
}
block.labels.Add(ln)
children.AppendNode(ln)
}
before, labelsNode, from := parseBlockLabels(nativeBlock, from)
block.labels = labelsNode
children.AppendNode(labelsNode)
before, oBrace, from := from.Partition(nativeBlock.OpenBraceRange)
children.AppendUnstructuredTokens(before.Tokens())
children.AppendUnstructuredTokens(oBrace.Tokens())
block.open = children.AppendUnstructuredTokens(oBrace.Tokens())
// We go a bit out of order here: we go hunting for the closing brace
// so that we have a delimited body, but then we'll deal with the body
@ -342,7 +330,7 @@ func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments,
children.AppendNode(body)
children.AppendUnstructuredTokens(after.Tokens())
children.AppendUnstructuredTokens(cBrace.Tokens())
block.close = children.AppendUnstructuredTokens(cBrace.Tokens())
// stragglers
children.AppendUnstructuredTokens(from.Tokens())
@ -356,6 +344,34 @@ func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments,
return newNode(block)
}
func parseBlockLabels(nativeBlock *hclsyntax.Block, from inputTokens) (inputTokens, *node, inputTokens) {
labelsObj := newBlockLabels(nil)
children := labelsObj.children
var beforeAll inputTokens
for i, rng := range nativeBlock.LabelRanges {
var before, labelTokens inputTokens
before, labelTokens, from = from.Partition(rng)
if i == 0 {
beforeAll = before
} else {
children.AppendUnstructuredTokens(before.Tokens())
}
tokens := labelTokens.Tokens()
var ln *node
if len(tokens) == 1 && tokens[0].Type == hclsyntax.TokenIdent {
ln = newNode(newIdentifier(tokens[0]))
} else {
ln = newNode(newQuoted(tokens))
}
labelsObj.items.Add(ln)
children.AppendNode(ln)
}
after := from
return beforeAll, newNode(labelsObj), after
}
func parseExpression(nativeExpr hclsyntax.Expression, from inputTokens) *node {
expr := newExpression()
children := expr.inTree.children

View File

@ -8,15 +8,23 @@ import (
"github.com/zclconf/go-cty/cty"
)
func parseFileContent(buf []byte, filename string) (node, hcl.Diagnostics) {
tokens := scan(buf, pos{
Filename: filename,
Pos: hcl.Pos{
Byte: 0,
Line: 1,
Column: 1,
},
})
func parseFileContent(buf []byte, filename string, start hcl.Pos) (node, hcl.Diagnostics) {
tokens := scan(buf, pos{Filename: filename, Pos: start})
p := newPeeker(tokens)
node, diags := parseValue(p)
if len(diags) == 0 && p.Peek().Type != tokenEOF {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Extraneous data after value",
Detail: "Extra characters appear after the JSON value.",
Subject: p.Peek().Range.Ptr(),
})
}
return node, diags
}
func parseExpression(buf []byte, filename string, start hcl.Pos) (node, hcl.Diagnostics) {
tokens := scan(buf, pos{Filename: filename, Pos: start})
p := newPeeker(tokens)
node, diags := parseValue(p)
if len(diags) == 0 && p.Peek().Type != tokenEOF {

View File

@ -18,7 +18,16 @@ import (
// from its HasErrors method. If HasErrors returns true, the file represents
// the subset of data that was able to be parsed, which may be none.
func Parse(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
rootNode, diags := parseFileContent(src, filename)
return ParseWithStartPos(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1})
}
// ParseWithStartPos attempts to parse like json.Parse, but unlike json.Parse
// you can pass a start position of the given JSON as a hcl.Pos.
//
// In most cases json.Parse should be sufficient, but it can be useful for parsing
// a part of JSON with correct positions.
func ParseWithStartPos(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) {
rootNode, diags := parseFileContent(src, filename, start)
switch rootNode.(type) {
case *objectVal, *arrayVal:
@ -62,6 +71,20 @@ func Parse(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
return file, diags
}
// ParseExpression parses the given buffer as a standalone JSON expression,
// returning it as an instance of Expression.
func ParseExpression(src []byte, filename string) (hcl.Expression, hcl.Diagnostics) {
return ParseExpressionWithStartPos(src, filename, hcl.Pos{Byte: 0, Line: 1, Column: 1})
}
// ParseExpressionWithStartPos parses like json.ParseExpression, but unlike
// json.ParseExpression you can pass a start position of the given JSON
// expression as a hcl.Pos.
func ParseExpressionWithStartPos(src []byte, filename string, start hcl.Pos) (hcl.Expression, hcl.Diagnostics) {
node, diags := parseExpression(src, filename, start)
return &expression{src: node}, diags
}
// ParseFile is a convenience wrapper around Parse that first attempts to load
// data from the given filename, passing the result to Parse if successful.
//

View File

@ -76,7 +76,10 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
}
}
has := collection.HasIndex(key)
// Here we drop marks from HasIndex result, in order to allow basic
// traversal of a marked list, tuple, or map in the same way we can
// traverse a marked object
has, _ := collection.HasIndex(key).Unmark()
if !has.IsKnown() {
if ty.IsTupleType() {
return cty.DynamicVal, nil
@ -217,7 +220,12 @@ func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagno
}
idx := cty.StringVal(attrName)
if obj.HasIndex(idx).False() {
// Here we drop marks from HasIndex result, in order to allow basic
// traversal of a marked map in the same way we can traverse a marked
// object
hasIndex, _ := obj.HasIndex(idx).Unmark()
if hasIndex.False() {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,