Merge pull request #9142 from hashicorp/f-hclv2-2.3

Support HCLv2 for Nomad jobs
This commit is contained in:
Mahmood Ali
2020-10-22 12:26:28 -05:00
committed by GitHub
149 changed files with 17330 additions and 760 deletions

View File

@@ -15,9 +15,9 @@ const (
// Constraint is used to serialize a job placement constraint.
type Constraint struct {
LTarget string
RTarget string
Operand string
LTarget string `hcl:"attribute,optional"`
RTarget string `hcl:"value,optional"`
Operand string `hcl:"operator,optional"`
}
// NewConstraint generates a new job placement constraint.

View File

@@ -89,9 +89,9 @@ const (
)
type CSIMountOptions struct {
FSType string `hcl:"fs_type"`
MountFlags []string `hcl:"mount_flags"`
ExtraKeysHCL []string `hcl:",unusedKeys" json:"-"` // report unexpected keys
FSType string `hcl:"fs_type,optional"`
MountFlags []string `hcl:"mount_flags,optional"`
ExtraKeysHCL []string `hcl1:",unusedKeys" json:"-"` // report unexpected keys
}
type CSISecrets map[string]string
@@ -133,7 +133,7 @@ type CSIVolume struct {
ModifyIndex uint64
// ExtraKeysHCL is used by the hcl parser to report unexpected keys
ExtraKeysHCL []string `hcl:",unusedKeys" json:"-"`
ExtraKeysHCL []string `hcl1:",unusedKeys" json:"-"`
}
// allocs is called after we query the volume (creating this CSIVolume struct) to collapse

View File

@@ -50,6 +50,9 @@ type JobsParseRequest struct {
// JobHCL is an hcl jobspec
JobHCL string
// HCLv1 indicates whether the JobHCL should be parsed with the hcl v1 parser
HCLv1 bool `json:"hclv1,omitempty"`
// Canonicalize is a flag as to if the server should return default values
// for unset fields
Canonicalize bool
@@ -439,15 +442,15 @@ type periodicForceResponse struct {
// UpdateStrategy defines a task groups update strategy.
type UpdateStrategy struct {
Stagger *time.Duration `mapstructure:"stagger"`
MaxParallel *int `mapstructure:"max_parallel"`
HealthCheck *string `mapstructure:"health_check"`
MinHealthyTime *time.Duration `mapstructure:"min_healthy_time"`
HealthyDeadline *time.Duration `mapstructure:"healthy_deadline"`
ProgressDeadline *time.Duration `mapstructure:"progress_deadline"`
Canary *int `mapstructure:"canary"`
AutoRevert *bool `mapstructure:"auto_revert"`
AutoPromote *bool `mapstructure:"auto_promote"`
Stagger *time.Duration `mapstructure:"stagger" hcl:"stagger,optional"`
MaxParallel *int `mapstructure:"max_parallel" hcl:"max_parallel,optional"`
HealthCheck *string `mapstructure:"health_check" hcl:"health_check,optional"`
MinHealthyTime *time.Duration `mapstructure:"min_healthy_time" hcl:"min_healthy_time,optional"`
HealthyDeadline *time.Duration `mapstructure:"healthy_deadline" hcl:"healthy_deadline,optional"`
ProgressDeadline *time.Duration `mapstructure:"progress_deadline" hcl:"progress_deadline,optional"`
Canary *int `mapstructure:"canary" hcl:"canary,optional"`
AutoRevert *bool `mapstructure:"auto_revert" hcl:"auto_revert,optional"`
AutoPromote *bool `mapstructure:"auto_promote" hcl:"auto_promote,optional"`
}
// DefaultUpdateStrategy provides a baseline that can be used to upgrade
@@ -640,8 +643,8 @@ func (u *UpdateStrategy) Empty() bool {
}
type Multiregion struct {
Strategy *MultiregionStrategy
Regions []*MultiregionRegion
Strategy *MultiregionStrategy `hcl:"strategy,block"`
Regions []*MultiregionRegion `hcl:"region,block"`
}
func (m *Multiregion) Canonicalize() {
@@ -700,24 +703,24 @@ func (m *Multiregion) Copy() *Multiregion {
}
type MultiregionStrategy struct {
MaxParallel *int `mapstructure:"max_parallel"`
OnFailure *string `mapstructure:"on_failure"`
MaxParallel *int `mapstructure:"max_parallel" hcl:"max_parallel,optional"`
OnFailure *string `mapstructure:"on_failure" hcl:"on_failure,optional"`
}
type MultiregionRegion struct {
Name string
Count *int
Datacenters []string
Meta map[string]string
Name string `hcl:",label"`
Count *int `hcl:"count,optional"`
Datacenters []string `hcl:"datacenters,optional"`
Meta map[string]string `hcl:"meta,block"`
}
// PeriodicConfig is for serializing periodic config for a job.
type PeriodicConfig struct {
Enabled *bool
Spec *string
Enabled *bool `hcl:"enabled,optional"`
Spec *string `hcl:"cron,optional"`
SpecType *string
ProhibitOverlap *bool `mapstructure:"prohibit_overlap"`
TimeZone *string `mapstructure:"time_zone"`
ProhibitOverlap *bool `mapstructure:"prohibit_overlap" hcl:"prohibit_overlap,optional"`
TimeZone *string `mapstructure:"time_zone" hcl:"time_zone,optional"`
}
func (p *PeriodicConfig) Canonicalize() {
@@ -779,38 +782,43 @@ func (p *PeriodicConfig) GetLocation() (*time.Location, error) {
// ParameterizedJobConfig is used to configure the parameterized job.
type ParameterizedJobConfig struct {
Payload string
MetaRequired []string `mapstructure:"meta_required"`
MetaOptional []string `mapstructure:"meta_optional"`
Payload string `hcl:"payload,optional"`
MetaRequired []string `mapstructure:"meta_required" hcl:"meta_required,optional"`
MetaOptional []string `mapstructure:"meta_optional" hcl:"meta_optional,optional"`
}
// Job is used to serialize a job.
type Job struct {
/* Fields parsed from HCL config */
Region *string `hcl:"region,optional"`
Namespace *string `hcl:"namespace,optional"`
ID *string `hcl:"id,optional"`
Name *string `hcl:"name,optional"`
Type *string `hcl:"type,optional"`
Priority *int `hcl:"priority,optional"`
AllAtOnce *bool `mapstructure:"all_at_once" hcl:"all_at_once,optional"`
Datacenters []string `hcl:"datacenters,optional"`
Constraints []*Constraint `hcl:"constraint,block"`
Affinities []*Affinity `hcl:"affinity,block"`
TaskGroups []*TaskGroup `hcl:"group,block"`
Update *UpdateStrategy `hcl:"update,block"`
Multiregion *Multiregion `hcl:"multiregion,block"`
Spreads []*Spread `hcl:"spread,block"`
Periodic *PeriodicConfig `hcl:"periodic,block"`
ParameterizedJob *ParameterizedJobConfig `hcl:"parameterized,block"`
Reschedule *ReschedulePolicy `hcl:"reschedule,block"`
Migrate *MigrateStrategy `hcl:"migrate,block"`
Meta map[string]string `hcl:"meta,block"`
ConsulToken *string `mapstructure:"consul_token" hcl:"consul_token,optional"`
VaultToken *string `mapstructure:"vault_token" hcl:"vault_token,optional"`
/* Fields set by server, not sourced from job config file */
Stop *bool
Region *string
Namespace *string
ID *string
ParentID *string
Name *string
Type *string
Priority *int
AllAtOnce *bool `mapstructure:"all_at_once"`
Datacenters []string
Constraints []*Constraint
Affinities []*Affinity
TaskGroups []*TaskGroup
Update *UpdateStrategy
Multiregion *Multiregion
Spreads []*Spread
Periodic *PeriodicConfig
ParameterizedJob *ParameterizedJobConfig
Dispatched bool
Payload []byte
Reschedule *ReschedulePolicy
Migrate *MigrateStrategy
Meta map[string]string
ConsulToken *string `mapstructure:"consul_token"`
VaultToken *string `mapstructure:"vault_token"`
VaultNamespace *string `mapstructure:"vault_namespace"`
NomadTokenID *string `mapstructure:"nomad_token_id"`
Status *string

View File

@@ -7,17 +7,17 @@ import (
// Resources encapsulates the required resources of
// a given task or task group.
type Resources struct {
CPU *int
MemoryMB *int `mapstructure:"memory"`
DiskMB *int `mapstructure:"disk"`
Networks []*NetworkResource
Devices []*RequestedDevice
CPU *int `hcl:"cpu,optional"`
MemoryMB *int `mapstructure:"memory" hcl:"memory,optional"`
DiskMB *int `mapstructure:"disk" hcl:"disk,optional"`
Networks []*NetworkResource `hcl:"network,block"`
Devices []*RequestedDevice `hcl:"device,block"`
// COMPAT(0.10)
// XXX Deprecated. Please do not use. The field will be removed in Nomad
// 0.10 and is only being kept to allow any references to be removed before
// then.
IOPS *int
IOPS *int `hcl:"iops,optional"`
}
// Canonicalize will supply missing values in the cases
@@ -84,29 +84,29 @@ func (r *Resources) Merge(other *Resources) {
}
type Port struct {
Label string
Value int `mapstructure:"static"`
To int `mapstructure:"to"`
HostNetwork string `mapstructure:"host_network"`
Label string `hcl:",label"`
Value int `mapstructure:"static" hcl:"static,optional"`
To int `mapstructure:"to" hcl:"to,optional"`
HostNetwork string `mapstructure:"host_network" hcl:"host_network,optional"`
}
type DNSConfig struct {
Servers []string `mapstructure:"servers"`
Searches []string `mapstructure:"searches"`
Options []string `mapstructure:"options"`
Servers []string `mapstructure:"servers" hcl:"servers,optional"`
Searches []string `mapstructure:"searches" hcl:"searches,optional"`
Options []string `mapstructure:"options" hcl:"options,optional"`
}
// NetworkResource is used to describe required network
// resources of a given task.
type NetworkResource struct {
Mode string
Device string
CIDR string
IP string
MBits *int
DNS *DNSConfig
ReservedPorts []Port
DynamicPorts []Port
Mode string `hcl:"mode,optional"`
Device string `hcl:"device,optional"`
CIDR string `hcl:"cidr,optional"`
IP string `hcl:"ip,optional"`
MBits *int `hcl:"mbits,optional"`
DNS *DNSConfig `hcl:"dns,block"`
ReservedPorts []Port `hcl:"reserved_ports,block"`
DynamicPorts []Port `hcl:"port,block"`
}
func (n *NetworkResource) Canonicalize() {
@@ -226,18 +226,18 @@ type RequestedDevice struct {
// * "gpu"
// * "nvidia/gpu"
// * "nvidia/gpu/GTX2080Ti"
Name string
Name string `hcl:",label"`
// Count is the number of requested devices
Count *uint64
Count *uint64 `hcl:"count,optional"`
// Constraints are a set of constraints to apply when selecting the device
// to use.
Constraints []*Constraint
Constraints []*Constraint `hcl:"constraint,block"`
// Affinities are a set of affinites to apply when selecting the device
// to use.
Affinities []*Affinity
Affinities []*Affinity `hcl:"affinity,block"`
}
func (d *RequestedDevice) Canonicalize() {

View File

@@ -60,14 +60,19 @@ type ScalingRequest struct {
// ScalingPolicy is the user-specified API object for an autoscaling policy
type ScalingPolicy struct {
/* fields set by user in HCL config */
Min *int64 `hcl:"min,optional"`
Max *int64 `hcl:"max,optional"`
Policy map[string]interface{} `hcl:"policy,block"`
Enabled *bool `hcl:"enabled,optional"`
/* fields set by server */
ID string
Namespace string
Type string
Target map[string]string
Min *int64
Max *int64
Policy map[string]interface{}
Enabled *bool
CreateIndex uint64
ModifyIndex uint64
}

View File

@@ -8,9 +8,9 @@ import (
// CheckRestart describes if and when a task should be restarted based on
// failing health checks.
type CheckRestart struct {
Limit int `mapstructure:"limit"`
Grace *time.Duration `mapstructure:"grace"`
IgnoreWarnings bool `mapstructure:"ignore_warnings"`
Limit int `mapstructure:"limit" hcl:"limit,optional"`
Grace *time.Duration `mapstructure:"grace" hcl:"grace,optional"`
IgnoreWarnings bool `mapstructure:"ignore_warnings" hcl:"ignore_warnings,optional"`
}
// Canonicalize CheckRestart fields if not nil.
@@ -73,46 +73,46 @@ func (c *CheckRestart) Merge(o *CheckRestart) *CheckRestart {
// ServiceCheck represents the consul health check that Nomad registers.
type ServiceCheck struct {
//FIXME Id is unused. Remove?
Id string
Name string
Type string
Command string
Args []string
Path string
Protocol string
PortLabel string `mapstructure:"port"`
Expose bool
AddressMode string `mapstructure:"address_mode"`
Interval time.Duration
Timeout time.Duration
InitialStatus string `mapstructure:"initial_status"`
TLSSkipVerify bool `mapstructure:"tls_skip_verify"`
Header map[string][]string
Method string
CheckRestart *CheckRestart `mapstructure:"check_restart"`
GRPCService string `mapstructure:"grpc_service"`
GRPCUseTLS bool `mapstructure:"grpc_use_tls"`
TaskName string `mapstructure:"task"`
SuccessBeforePassing int `mapstructure:"success_before_passing"`
FailuresBeforeCritical int `mapstructure:"failures_before_critical"`
Id string `hcl:"id,optional"`
Name string `hcl:"name,optional"`
Type string `hcl:"type,optional"`
Command string `hcl:"command,optional"`
Args []string `hcl:"args,optional"`
Path string `hcl:"path,optional"`
Protocol string `hcl:"protocol,optional"`
PortLabel string `mapstructure:"port" hcl:"port,optional"`
Expose bool `hcl:"expose,optional"`
AddressMode string `mapstructure:"address_mode" hcl:"address_mode,optional"`
Interval time.Duration `hcl:"interval,optional"`
Timeout time.Duration `hcl:"timeout,optional"`
InitialStatus string `mapstructure:"initial_status" hcl:"initial_status,optional"`
TLSSkipVerify bool `mapstructure:"tls_skip_verify" hcl:"tls_skip_verify,optional"`
Header map[string][]string `hcl:"header,block"`
Method string `hcl:"method,optional"`
CheckRestart *CheckRestart `mapstructure:"check_restart" hcl:"check_restart,block"`
GRPCService string `mapstructure:"grpc_service" hcl:"grpc_service,optional"`
GRPCUseTLS bool `mapstructure:"grpc_use_tls" hcl:"grpc_use_tls,optional"`
TaskName string `mapstructure:"task" hcl:"task,optional"`
SuccessBeforePassing int `mapstructure:"success_before_passing" hcl:"success_before_passing,optional"`
FailuresBeforeCritical int `mapstructure:"failures_before_critical" hcl:"failures_before_critical,optional"`
}
// Service represents a Consul service definition.
type Service struct {
//FIXME Id is unused. Remove?
Id string
Name string
Tags []string
CanaryTags []string `mapstructure:"canary_tags"`
EnableTagOverride bool `mapstructure:"enable_tag_override"`
PortLabel string `mapstructure:"port"`
AddressMode string `mapstructure:"address_mode"`
Checks []ServiceCheck
CheckRestart *CheckRestart `mapstructure:"check_restart"`
Connect *ConsulConnect
Meta map[string]string
CanaryMeta map[string]string
TaskName string `mapstructure:"task"`
Id string `hcl:"id,optional"`
Name string `hcl:"name,optional"`
Tags []string `hcl:"tags,optional"`
CanaryTags []string `mapstructure:"canary_tags" hcl:"canary_tags,optional"`
EnableTagOverride bool `mapstructure:"enable_tag_override" hcl:"enable_tag_override,optional"`
PortLabel string `mapstructure:"port" hcl:"port,optional"`
AddressMode string `mapstructure:"address_mode" hcl:"address_mode,optional"`
Checks []ServiceCheck `hcl:"check,block"`
CheckRestart *CheckRestart `mapstructure:"check_restart" hcl:"check_restart,block"`
Connect *ConsulConnect `hcl:"connect,block"`
Meta map[string]string `hcl:"meta,block"`
CanaryMeta map[string]string `hcl:"canary_meta,block"`
TaskName string `mapstructure:"task" hcl:"task,optional"`
}
// Canonicalize the Service by ensuring its name and address mode are set. Task
@@ -151,10 +151,10 @@ func (s *Service) Canonicalize(t *Task, tg *TaskGroup, job *Job) {
// ConsulConnect represents a Consul Connect jobspec stanza.
type ConsulConnect struct {
Native bool
Gateway *ConsulGateway
SidecarService *ConsulSidecarService `mapstructure:"sidecar_service"`
SidecarTask *SidecarTask `mapstructure:"sidecar_task"`
Native bool `hcl:"native,optional"`
Gateway *ConsulGateway `hcl:"gateway,block"`
SidecarService *ConsulSidecarService `mapstructure:"sidecar_service" hcl:"sidecar_service,block"`
SidecarTask *SidecarTask `mapstructure:"sidecar_task" hcl:"sidecar_task,block"`
}
func (cc *ConsulConnect) Canonicalize() {
@@ -170,9 +170,9 @@ func (cc *ConsulConnect) Canonicalize() {
// ConsulSidecarService represents a Consul Connect SidecarService jobspec
// stanza.
type ConsulSidecarService struct {
Tags []string
Port string
Proxy *ConsulProxy
Tags []string `hcl:"tags,optional"`
Port string `hcl:"port,optional"`
Proxy *ConsulProxy `hcl:"proxy,block"`
}
func (css *ConsulSidecarService) Canonicalize() {
@@ -190,17 +190,17 @@ func (css *ConsulSidecarService) Canonicalize() {
// SidecarTask represents a subset of Task fields that can be set to override
// the fields of the Task generated for the sidecar
type SidecarTask struct {
Name string
Driver string
User string
Config map[string]interface{}
Env map[string]string
Resources *Resources
Meta map[string]string
KillTimeout *time.Duration `mapstructure:"kill_timeout"`
LogConfig *LogConfig `mapstructure:"logs"`
ShutdownDelay *time.Duration `mapstructure:"shutdown_delay"`
KillSignal string `mapstructure:"kill_signal"`
Name string `hcl:"name,optional"`
Driver string `hcl:"driver,optional"`
User string `hcl:"user,optional"`
Config map[string]interface{} `hcl:"config,block"`
Env map[string]string `hcl:"env,block"`
Resources *Resources `hcl:"resources,block"`
Meta map[string]string `hcl:"meta,block"`
KillTimeout *time.Duration `mapstructure:"kill_timeout" hcl:"kill_timeout,optional"`
LogConfig *LogConfig `mapstructure:"logs" hcl:"logs,block"`
ShutdownDelay *time.Duration `mapstructure:"shutdown_delay" hcl:"shutdown_delay,optional"`
KillSignal string `mapstructure:"kill_signal" hcl:"kill_signal,optional"`
}
func (st *SidecarTask) Canonicalize() {
@@ -243,11 +243,11 @@ func (st *SidecarTask) Canonicalize() {
// ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza.
type ConsulProxy struct {
LocalServiceAddress string `mapstructure:"local_service_address"`
LocalServicePort int `mapstructure:"local_service_port"`
ExposeConfig *ConsulExposeConfig `mapstructure:"expose"`
Upstreams []*ConsulUpstream
Config map[string]interface{}
LocalServiceAddress string `mapstructure:"local_service_address" hcl:"local_service_address,optional"`
LocalServicePort int `mapstructure:"local_service_port" hcl:"local_service_port,optional"`
ExposeConfig *ConsulExposeConfig `mapstructure:"expose" hcl:"expose,block"`
Upstreams []*ConsulUpstream `hcl:"upstreams,block"`
Config map[string]interface{} `hcl:"config,block"`
}
func (cp *ConsulProxy) Canonicalize() {
@@ -268,12 +268,12 @@ func (cp *ConsulProxy) Canonicalize() {
// ConsulUpstream represents a Consul Connect upstream jobspec stanza.
type ConsulUpstream struct {
DestinationName string `mapstructure:"destination_name"`
LocalBindPort int `mapstructure:"local_bind_port"`
DestinationName string `mapstructure:"destination_name" hcl:"destination_name,optional"`
LocalBindPort int `mapstructure:"local_bind_port" hcl:"local_bind_port,optional"`
}
type ConsulExposeConfig struct {
Path []*ConsulExposePath `mapstructure:"path"`
Path []*ConsulExposePath `mapstructure:"path" hcl:"path,block"`
}
func (cec *ConsulExposeConfig) Canonicalize() {
@@ -287,19 +287,19 @@ func (cec *ConsulExposeConfig) Canonicalize() {
}
type ConsulExposePath struct {
Path string
Protocol string
LocalPathPort int `mapstructure:"local_path_port"`
ListenerPort string `mapstructure:"listener_port"`
Path string `hcl:"path,optional"`
Protocol string `hcl:"protocol,optional"`
LocalPathPort int `mapstructure:"local_path_port" hcl:"local_path_port,optional"`
ListenerPort string `mapstructure:"listener_port" hcl:"listener_port,optional"`
}
// ConsulGateway is used to configure one of the Consul Connect Gateway types.
type ConsulGateway struct {
// Proxy is used to configure the Envoy instance acting as the gateway.
Proxy *ConsulGatewayProxy
Proxy *ConsulGatewayProxy `hcl:"proxy,block"`
// Ingress represents the Consul Configuration Entry for an Ingress Gateway.
Ingress *ConsulIngressConfigEntry
Ingress *ConsulIngressConfigEntry `hcl:"ingress,block"`
// Terminating is not yet supported.
// Terminating *ConsulTerminatingConfigEntry
@@ -328,8 +328,9 @@ func (g *ConsulGateway) Copy() *ConsulGateway {
}
type ConsulGatewayBindAddress struct {
Address string `mapstructure:"address"`
Port int `mapstructure:"port"`
Name string `hcl:",label"`
Address string `mapstructure:"address" hcl:"address,optional"`
Port int `mapstructure:"port" hcl:"port,optional"`
}
var (
@@ -341,11 +342,11 @@ var (
//
// https://www.consul.io/docs/connect/proxies/envoy#gateway-options
type ConsulGatewayProxy struct {
ConnectTimeout *time.Duration `mapstructure:"connect_timeout"`
EnvoyGatewayBindTaggedAddresses bool `mapstructure:"envoy_gateway_bind_tagged_addresses"`
EnvoyGatewayBindAddresses map[string]*ConsulGatewayBindAddress `mapstructure:"envoy_gateway_bind_addresses"`
EnvoyGatewayNoDefaultBind bool `mapstructure:"envoy_gateway_no_default_bind"`
Config map[string]interface{} // escape hatch envoy config
ConnectTimeout *time.Duration `mapstructure:"connect_timeout" hcl:"connect_timeout,optional"`
EnvoyGatewayBindTaggedAddresses bool `mapstructure:"envoy_gateway_bind_tagged_addresses" hcl:"envoy_gateway_bind_tagged_addresses,optional"`
EnvoyGatewayBindAddresses map[string]*ConsulGatewayBindAddress `mapstructure:"envoy_gateway_bind_addresses" hcl:"envoy_gateway_bind_addresses,block"`
EnvoyGatewayNoDefaultBind bool `mapstructure:"envoy_gateway_no_default_bind" hcl:"envoy_gateway_no_default_bind,optional"`
Config map[string]interface{} `hcl:"config,block"` // escape hatch envoy config
}
func (p *ConsulGatewayProxy) Canonicalize() {
@@ -399,7 +400,7 @@ func (p *ConsulGatewayProxy) Copy() *ConsulGatewayProxy {
// ConsulGatewayTLSConfig is used to configure TLS for a gateway.
type ConsulGatewayTLSConfig struct {
Enabled bool
Enabled bool `hcl:"enabled,optional"`
}
func (tc *ConsulGatewayTLSConfig) Canonicalize() {
@@ -419,9 +420,9 @@ func (tc *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig {
type ConsulIngressService struct {
// Namespace is not yet supported.
// Namespace string
Name string
Name string `hcl:"name,optional"`
Hosts []string
Hosts []string `hcl:"hosts,optional"`
}
func (s *ConsulIngressService) Canonicalize() {
@@ -458,9 +459,9 @@ const (
// ConsulIngressListener is used to configure a listener on a Consul Ingress
// Gateway.
type ConsulIngressListener struct {
Port int
Protocol string
Services []*ConsulIngressService
Port int `hcl:"port,optional"`
Protocol string `hcl:"protocol,optional"`
Services []*ConsulIngressService `hcl:"service,block"`
}
func (l *ConsulIngressListener) Canonicalize() {
@@ -506,8 +507,8 @@ type ConsulIngressConfigEntry struct {
// Namespace is not yet supported.
// Namespace string
TLS *ConsulGatewayTLSConfig
Listeners []*ConsulIngressListener
TLS *ConsulGatewayTLSConfig `hcl:"tls,block"`
Listeners []*ConsulIngressListener `hcl:"listener,block"`
}
func (e *ConsulIngressConfigEntry) Canonicalize() {

View File

@@ -67,10 +67,10 @@ type AllocResourceUsage struct {
// RestartPolicy defines how the Nomad client restarts
// tasks in a taskgroup when they fail
type RestartPolicy struct {
Interval *time.Duration
Attempts *int
Delay *time.Duration
Mode *string
Interval *time.Duration `hcl:"interval,optional"`
Attempts *int `hcl:"attempts,optional"`
Delay *time.Duration `hcl:"delay,optional"`
Mode *string `hcl:"mode,optional"`
}
func (r *RestartPolicy) Merge(rp *RestartPolicy) {
@@ -91,24 +91,24 @@ func (r *RestartPolicy) Merge(rp *RestartPolicy) {
// Reschedule configures how Tasks are rescheduled when they crash or fail.
type ReschedulePolicy struct {
// Attempts limits the number of rescheduling attempts that can occur in an interval.
Attempts *int `mapstructure:"attempts"`
Attempts *int `mapstructure:"attempts" hcl:"attempts,optional"`
// Interval is a duration in which we can limit the number of reschedule attempts.
Interval *time.Duration `mapstructure:"interval"`
Interval *time.Duration `mapstructure:"interval" hcl:"interval,optional"`
// Delay is a minimum duration to wait between reschedule attempts.
// The delay function determines how much subsequent reschedule attempts are delayed by.
Delay *time.Duration `mapstructure:"delay"`
Delay *time.Duration `mapstructure:"delay" hcl:"delay,optional"`
// DelayFunction determines how the delay progressively changes on subsequent reschedule
// attempts. Valid values are "exponential", "constant", and "fibonacci".
DelayFunction *string `mapstructure:"delay_function"`
DelayFunction *string `mapstructure:"delay_function" hcl:"delay_function,optional"`
// MaxDelay is an upper bound on the delay.
MaxDelay *time.Duration `mapstructure:"max_delay"`
MaxDelay *time.Duration `mapstructure:"max_delay" hcl:"max_delay,optional"`
// Unlimited allows rescheduling attempts until they succeed
Unlimited *bool `mapstructure:"unlimited"`
Unlimited *bool `mapstructure:"unlimited" hcl:"unlimited,optional"`
}
func (r *ReschedulePolicy) Merge(rp *ReschedulePolicy) {
@@ -159,10 +159,10 @@ func (r *ReschedulePolicy) Canonicalize(jobType string) {
// Affinity is used to serialize task group affinities
type Affinity struct {
LTarget string // Left-hand target
RTarget string // Right-hand target
Operand string // Constraint operand (<=, <, =, !=, >, >=), set_contains_all, set_contains_any
Weight *int8 // Weight applied to nodes that match the affinity. Can be negative
LTarget string `hcl:"attribute,optional"` // Left-hand target
RTarget string `hcl:"value,optional"` // Right-hand target
Operand string `hcl:"operator,optional"` // Constraint operand (<=, <, =, !=, >, >=), set_contains_all, set_contains_any
Weight *int8 `hcl:"weight,optional"` // Weight applied to nodes that match the affinity. Can be negative
}
func NewAffinity(LTarget string, Operand string, RTarget string, Weight int8) *Affinity {
@@ -255,15 +255,15 @@ func (p *ReschedulePolicy) String() string {
// Spread is used to serialize task group allocation spread preferences
type Spread struct {
Attribute string
Weight *int8
SpreadTarget []*SpreadTarget
Attribute string `hcl:"attribute,optional"`
Weight *int8 `hcl:"weight,optional"`
SpreadTarget []*SpreadTarget `hcl:"target,block"`
}
// SpreadTarget is used to serialize target allocation spread percentages
type SpreadTarget struct {
Value string
Percent uint8
Value string `hcl:",label"`
Percent uint8 `hcl:"percent,optional"`
}
func NewSpreadTarget(value string, percent uint8) *SpreadTarget {
@@ -289,9 +289,9 @@ func (s *Spread) Canonicalize() {
// EphemeralDisk is an ephemeral disk object
type EphemeralDisk struct {
Sticky *bool
Migrate *bool
SizeMB *int `mapstructure:"size"`
Sticky *bool `hcl:"sticky,optional"`
Migrate *bool `hcl:"migrate,optional"`
SizeMB *int `mapstructure:"size" hcl:"size,optional"`
}
func DefaultEphemeralDisk() *EphemeralDisk {
@@ -317,10 +317,10 @@ func (e *EphemeralDisk) Canonicalize() {
// MigrateStrategy describes how allocations for a task group should be
// migrated between nodes (eg when draining).
type MigrateStrategy struct {
MaxParallel *int `mapstructure:"max_parallel"`
HealthCheck *string `mapstructure:"health_check"`
MinHealthyTime *time.Duration `mapstructure:"min_healthy_time"`
HealthyDeadline *time.Duration `mapstructure:"healthy_deadline"`
MaxParallel *int `mapstructure:"max_parallel" hcl:"max_parallel,optional"`
HealthCheck *string `mapstructure:"health_check" hcl:"health_check,optional"`
MinHealthyTime *time.Duration `mapstructure:"min_healthy_time" hcl:"min_healthy_time,optional"`
HealthyDeadline *time.Duration `mapstructure:"healthy_deadline" hcl:"healthy_deadline,optional"`
}
func DefaultMigrateStrategy() *MigrateStrategy {
@@ -377,12 +377,12 @@ func (m *MigrateStrategy) Copy() *MigrateStrategy {
// VolumeRequest is a representation of a storage volume that a TaskGroup wishes to use.
type VolumeRequest struct {
Name string
Type string
Source string
ReadOnly bool `hcl:"read_only"`
MountOptions *CSIMountOptions `hcl:"mount_options"`
ExtraKeysHCL []string `hcl:",unusedKeys" json:"-"`
Name string `hcl:"name,label"`
Type string `hcl:"type,optional"`
Source string `hcl:"source,optional"`
ReadOnly bool `hcl:"read_only,optional"`
MountOptions *CSIMountOptions `hcl:"mount_options,block"`
ExtraKeysHCL []string `hcl1:",unusedKeys,optional" json:"-"`
}
const (
@@ -394,10 +394,10 @@ const (
// VolumeMount represents the relationship between a destination path in a task
// and the task group volume that should be mounted there.
type VolumeMount struct {
Volume *string
Destination *string
ReadOnly *bool `mapstructure:"read_only"`
PropagationMode *string `mapstructure:"propagation_mode"`
Volume *string `hcl:"volume,optional"`
Destination *string `hcl:"destination,optional"`
ReadOnly *bool `mapstructure:"read_only" hcl:"read_only,optional"`
PropagationMode *string `mapstructure:"propagation_mode" hcl:"propagation_mode,optional"`
}
func (vm *VolumeMount) Canonicalize() {
@@ -411,24 +411,24 @@ func (vm *VolumeMount) Canonicalize() {
// TaskGroup is the unit of scheduling.
type TaskGroup struct {
Name *string
Count *int
Constraints []*Constraint
Affinities []*Affinity
Tasks []*Task
Spreads []*Spread
Volumes map[string]*VolumeRequest
RestartPolicy *RestartPolicy
ReschedulePolicy *ReschedulePolicy
EphemeralDisk *EphemeralDisk
Update *UpdateStrategy
Migrate *MigrateStrategy
Networks []*NetworkResource
Meta map[string]string
Services []*Service
ShutdownDelay *time.Duration `mapstructure:"shutdown_delay"`
StopAfterClientDisconnect *time.Duration `mapstructure:"stop_after_client_disconnect"`
Scaling *ScalingPolicy
Name *string `hcl:"name,label"`
Count *int `hcl:"count,optional"`
Constraints []*Constraint `hcl:"constraint,block"`
Affinities []*Affinity `hcl:"affinity,block"`
Tasks []*Task `hcl:"task,block"`
Spreads []*Spread `hcl:"spread,block"`
Volumes map[string]*VolumeRequest `hcl:"volume,block"`
RestartPolicy *RestartPolicy `hcl:"restart,block"`
ReschedulePolicy *ReschedulePolicy `hcl:"reschedule,block"`
EphemeralDisk *EphemeralDisk `hcl:"ephemeral_disk,block"`
Update *UpdateStrategy `hcl:"update,block"`
Migrate *MigrateStrategy `hcl:"migrate,block"`
Networks []*NetworkResource `hcl:"network,block"`
Meta map[string]string `hcl:"meta,block"`
Services []*Service `hcl:"service,block"`
ShutdownDelay *time.Duration `mapstructure:"shutdown_delay" hcl:"shutdown_delay,optional"`
StopAfterClientDisconnect *time.Duration `mapstructure:"stop_after_client_disconnect" hcl:"stop_after_client_disconnect,optional"`
Scaling *ScalingPolicy `hcl:"scaling,block"`
}
// NewTaskGroup creates a new TaskGroup.
@@ -605,8 +605,8 @@ func (g *TaskGroup) AddSpread(s *Spread) *TaskGroup {
// LogConfig provides configuration for log rotation
type LogConfig struct {
MaxFiles *int `mapstructure:"max_files"`
MaxFileSizeMB *int `mapstructure:"max_file_size"`
MaxFiles *int `mapstructure:"max_files" hcl:"max_files,optional"`
MaxFileSizeMB *int `mapstructure:"max_file_size" hcl:"max_file_size,optional"`
}
func DefaultLogConfig() *LogConfig {
@@ -627,17 +627,17 @@ func (l *LogConfig) Canonicalize() {
// DispatchPayloadConfig configures how a task gets its input from a job dispatch
type DispatchPayloadConfig struct {
File string
File string `hcl:"file,optional"`
}
const (
TaskLifecycleHookPrestart = "prestart"
TaskLifecycleHookPrestart = "prestart"
TaskLifecycleHookPoststart = "poststart"
)
type TaskLifecycle struct {
Hook string `mapstructure:"hook"`
Sidecar bool `mapstructure:"sidecar"`
Hook string `mapstructure:"hook" hcl:"hook,optional"`
Sidecar bool `mapstructure:"sidecar" hcl:"sidecar,optional"`
}
// Determine if lifecycle has user-input values
@@ -647,30 +647,30 @@ func (l *TaskLifecycle) Empty() bool {
// Task is a single process in a task group.
type Task struct {
Name string
Driver string
User string
Lifecycle *TaskLifecycle
Config map[string]interface{}
Constraints []*Constraint
Affinities []*Affinity
Env map[string]string
Services []*Service
Resources *Resources
RestartPolicy *RestartPolicy
Meta map[string]string
KillTimeout *time.Duration `mapstructure:"kill_timeout"`
LogConfig *LogConfig `mapstructure:"logs"`
Artifacts []*TaskArtifact
Vault *Vault
Templates []*Template
DispatchPayload *DispatchPayloadConfig
VolumeMounts []*VolumeMount
CSIPluginConfig *TaskCSIPluginConfig `mapstructure:"csi_plugin" json:",omitempty"`
Leader bool
ShutdownDelay time.Duration `mapstructure:"shutdown_delay"`
KillSignal string `mapstructure:"kill_signal"`
Kind string
Name string `hcl:"name,label"`
Driver string `hcl:"driver,optional"`
User string `hcl:"user,optional"`
Lifecycle *TaskLifecycle `hcl:"lifecycle,block"`
Config map[string]interface{} `hcl:"config,block"`
Constraints []*Constraint `hcl:"constraint,block"`
Affinities []*Affinity `hcl:"affinity,block"`
Env map[string]string `hcl:"env,block"`
Services []*Service `hcl:"service,block"`
Resources *Resources `hcl:"resources,block"`
RestartPolicy *RestartPolicy `hcl:"restart,block"`
Meta map[string]string `hcl:"meta,block"`
KillTimeout *time.Duration `mapstructure:"kill_timeout" hcl:"kill_timeout,optional"`
LogConfig *LogConfig `mapstructure:"logs" hcl:"logs,block"`
Artifacts []*TaskArtifact `hcl:"artifact,block"`
Vault *Vault `hcl:"vault,block"`
Templates []*Template `hcl:"template,block"`
DispatchPayload *DispatchPayloadConfig `hcl:"dispatch_payload,block"`
VolumeMounts []*VolumeMount `hcl:"volume_mount,block"`
CSIPluginConfig *TaskCSIPluginConfig `mapstructure:"csi_plugin" json:",omitempty" hcl:"csi_plugin,block"`
Leader bool `hcl:"leader,optional"`
ShutdownDelay time.Duration `mapstructure:"shutdown_delay" hcl:"shutdown_delay,optional"`
KillSignal string `mapstructure:"kill_signal" hcl:"kill_signal,optional"`
Kind string `hcl:"kind,optional"`
}
func (t *Task) Canonicalize(tg *TaskGroup, job *Job) {
@@ -723,10 +723,10 @@ func (t *Task) Canonicalize(tg *TaskGroup, job *Job) {
// TaskArtifact is used to download artifacts before running a task.
type TaskArtifact struct {
GetterSource *string `mapstructure:"source"`
GetterOptions map[string]string `mapstructure:"options"`
GetterMode *string `mapstructure:"mode"`
RelativeDest *string `mapstructure:"destination"`
GetterSource *string `mapstructure:"source" hcl:"source,optional"`
GetterOptions map[string]string `mapstructure:"options" hcl:"options,block"`
GetterMode *string `mapstructure:"mode" hcl:"mode,optional"`
RelativeDest *string `mapstructure:"destination" hcl:"destination,optional"`
}
func (a *TaskArtifact) Canonicalize() {
@@ -753,17 +753,17 @@ func (a *TaskArtifact) Canonicalize() {
}
type Template struct {
SourcePath *string `mapstructure:"source"`
DestPath *string `mapstructure:"destination"`
EmbeddedTmpl *string `mapstructure:"data"`
ChangeMode *string `mapstructure:"change_mode"`
ChangeSignal *string `mapstructure:"change_signal"`
Splay *time.Duration `mapstructure:"splay"`
Perms *string `mapstructure:"perms"`
LeftDelim *string `mapstructure:"left_delimiter"`
RightDelim *string `mapstructure:"right_delimiter"`
Envvars *bool `mapstructure:"env"`
VaultGrace *time.Duration `mapstructure:"vault_grace"`
SourcePath *string `mapstructure:"source" hcl:"source,optional"`
DestPath *string `mapstructure:"destination" hcl:"destination,optional"`
EmbeddedTmpl *string `mapstructure:"data" hcl:"data,optional"`
ChangeMode *string `mapstructure:"change_mode" hcl:"change_mode,optional"`
ChangeSignal *string `mapstructure:"change_signal" hcl:"change_signal,optional"`
Splay *time.Duration `mapstructure:"splay" hcl:"splay,optional"`
Perms *string `mapstructure:"perms" hcl:"perms,optional"`
LeftDelim *string `mapstructure:"left_delimiter" hcl:"left_delimiter,optional"`
RightDelim *string `mapstructure:"right_delimiter" hcl:"right_delimiter,optional"`
Envvars *bool `mapstructure:"env" hcl:"env,optional"`
VaultGrace *time.Duration `mapstructure:"vault_grace" hcl:"vault_grace,optional"`
}
func (tmpl *Template) Canonicalize() {
@@ -812,11 +812,11 @@ func (tmpl *Template) Canonicalize() {
}
type Vault struct {
Policies []string
Namespace *string `mapstructure:"namespace"`
Env *bool
ChangeMode *string `mapstructure:"change_mode"`
ChangeSignal *string `mapstructure:"change_signal"`
Policies []string `hcl:"policies,optional"`
Namespace *string `mapstructure:"namespace" hcl:"namespace,optional"`
Env *bool `hcl:"env,optional"`
ChangeMode *string `mapstructure:"change_mode" hcl:"change_mode,optional"`
ChangeSignal *string `mapstructure:"change_signal" hcl:"change_signal,optional"`
}
func (v *Vault) Canonicalize() {
@@ -975,10 +975,10 @@ const (
type TaskCSIPluginConfig struct {
// ID is the identifier of the plugin.
// Ideally this should be the FQDN of the plugin.
ID string `mapstructure:"id"`
ID string `mapstructure:"id" hcl:"id,optional"`
// CSIPluginType instructs Nomad on how to handle processing a plugin
Type CSIPluginType `mapstructure:"type"`
Type CSIPluginType `mapstructure:"type" hcl:"type,optional"`
// MountDir is the destination that nomad should mount in its CSI
// directory for the plugin. It will then expect a file called CSISocketName
@@ -986,7 +986,7 @@ type TaskCSIPluginConfig struct {
// "MountDir/CSIIntermediaryDirname/VolumeName/AllocID for mounts.
//
// Default is /csi.
MountDir string `mapstructure:"mount_dir"`
MountDir string `mapstructure:"mount_dir" hcl:"mount_dir,optional"`
}
func (t *TaskCSIPluginConfig) Canonicalize() {

View File

@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/jobspec"
"github.com/hashicorp/nomad/jobspec2"
"github.com/hashicorp/nomad/nomad/structs"
)
@@ -675,7 +676,14 @@ func (s *HTTPServer) JobsParseRequest(resp http.ResponseWriter, req *http.Reques
}
jobfile := strings.NewReader(args.JobHCL)
jobStruct, err := jobspec.Parse(jobfile)
var jobStruct *api.Job
var err error
if args.HCLv1 {
jobStruct, err = jobspec.Parse(jobfile)
} else {
jobStruct, err = jobspec2.ParseWithArgs("input.hcl", jobfile, nil, false)
}
if err != nil {
return nil, CodedError(400, err.Error())
}

View File

@@ -7,6 +7,7 @@ import (
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
@@ -14,6 +15,7 @@ import (
gg "github.com/hashicorp/go-getter"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/jobspec"
"github.com/hashicorp/nomad/jobspec2"
"github.com/kr/text"
"github.com/mitchellh/cli"
"github.com/posener/complete"
@@ -378,13 +380,20 @@ READ:
}
type JobGetter struct {
hcl1 bool
// The fields below can be overwritten for tests
testStdin io.Reader
}
// StructJob returns the Job struct from jobfile.
func (j *JobGetter) ApiJob(jpath string) (*api.Job, error) {
return j.ApiJobWithArgs(jpath, nil)
}
func (j *JobGetter) ApiJobWithArgs(jpath string, vars map[string]string) (*api.Job, error) {
var jobfile io.Reader
pathName := filepath.Base(jpath)
switch jpath {
case "-":
if j.testStdin != nil {
@@ -392,6 +401,7 @@ func (j *JobGetter) ApiJob(jpath string) (*api.Job, error) {
} else {
jobfile = os.Stdin
}
pathName = "stdin.hcl"
default:
if len(jpath) == 0 {
return nil, fmt.Errorf("Error jobfile path has to be specified.")
@@ -432,9 +442,15 @@ func (j *JobGetter) ApiJob(jpath string) (*api.Job, error) {
}
// Parse the JobFile
jobStruct, err := jobspec.Parse(jobfile)
var jobStruct *api.Job
var err error
if j.hcl1 {
jobStruct, err = jobspec.Parse(jobfile)
} else {
jobStruct, err = jobspec2.ParseWithArgs(pathName, jobfile, vars, true)
}
if err != nil {
return nil, fmt.Errorf("Error parsing job file from %s: %v", jpath, err)
return nil, fmt.Errorf("Error parsing job file from %s:\n%v", jpath, err)
}
return jobStruct, nil
@@ -507,3 +523,25 @@ func (w *uiErrorWriter) Close() error {
}
return nil
}
// parseVars decodes a slice of `<key>=<val>` or `<key>` strings into a golang map.
//
// `<key>` without corresponding value, is mapped to the `<key>` environment variable.
func parseVars(vars []string) map[string]string {
if len(vars) == 0 {
return nil
}
result := make(map[string]string, len(vars))
for _, v := range vars {
parts := strings.SplitN(v, "=", 2)
k := parts[0]
if len(parts) == 2 {
result[k] = parts[1]
} else {
result[k] = os.Getenv(k)
}
}
return result
}

View File

@@ -209,20 +209,20 @@ func TestHelpers_LineLimitReader_TimeLimit(t *testing.T) {
const (
job = `job "job1" {
type = "service"
datacenters = [ "dc1" ]
group "group1" {
count = 1
task "task1" {
driver = "exec"
resources = {}
}
restart{
attempts = 10
mode = "delay"
interval = "15s"
}
}
type = "service"
datacenters = ["dc1"]
group "group1" {
count = 1
task "task1" {
driver = "exec"
resources {}
}
restart {
attempts = 10
mode = "delay"
interval = "15s"
}
}
}`
)
@@ -392,3 +392,14 @@ func TestUiErrorWriter(t *testing.T) {
expectedErr += "and thensome more\n"
require.Equal(t, expectedErr, errBuf.String())
}
func TestParseVars(t *testing.T) {
input := []string{"key1=val1", "HOME", "key2=321"}
expected := map[string]string{
"key1": "val1",
"HOME": os.Getenv("HOME"),
"key2": "321",
}
require.Equal(t, expected, parseVars(input))
}

View File

@@ -6,6 +6,7 @@ import (
"strings"
"time"
cflags "github.com/hashicorp/consul/command/flags"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/scheduler"
"github.com/posener/complete"
@@ -72,6 +73,9 @@ Plan Options:
Determines whether the diff between the remote job and planned job is shown.
Defaults to true.
-hcl1
Parses the job file as HCLv1.
-policy-override
Sets the flag to force override any soft mandatory Sentinel policies.
@@ -91,6 +95,8 @@ func (c *JobPlanCommand) AutocompleteFlags() complete.Flags {
"-diff": complete.PredictNothing,
"-policy-override": complete.PredictNothing,
"-verbose": complete.PredictNothing,
"-hcl1": complete.PredictNothing,
"-var": complete.PredictAnything,
})
}
@@ -101,12 +107,15 @@ func (c *JobPlanCommand) AutocompleteArgs() complete.Predictor {
func (c *JobPlanCommand) Name() string { return "job plan" }
func (c *JobPlanCommand) Run(args []string) int {
var diff, policyOverride, verbose bool
var varArgs cflags.AppendSliceValue
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.BoolVar(&diff, "diff", true, "")
flags.BoolVar(&policyOverride, "policy-override", false, "")
flags.BoolVar(&verbose, "verbose", false, "")
flags.BoolVar(&c.JobGetter.hcl1, "hcl1", false, "")
flags.Var(&varArgs, "var", "")
if err := flags.Parse(args); err != nil {
return 255
@@ -122,7 +131,7 @@ func (c *JobPlanCommand) Run(args []string) int {
path := args[0]
// Get Job struct from Jobfile
job, err := c.JobGetter.ApiJob(args[0])
job, err := c.JobGetter.ApiJobWithArgs(args[0], parseVars(varArgs))
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
return 255

View File

@@ -94,7 +94,7 @@ job "job1" {
count = 1
task "task1" {
driver = "exec"
resources = {
resources {
cpu = 1000
memory = 512
}
@@ -134,7 +134,7 @@ job "job1" {
count = 1
task "task1" {
driver = "exec"
resources = {
resources {
cpu = 1000
memory = 512
}

View File

@@ -9,6 +9,7 @@ import (
"strings"
"time"
cflags "github.com/hashicorp/consul/command/flags"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/helper"
"github.com/posener/complete"
@@ -79,6 +80,9 @@ Run Options:
the evaluation ID will be printed to the screen, which can be used to
examine the evaluation using the eval-status command.
-hcl1
Parses the job file as HCLv1.
-output
Output the JSON that would be submitted to the HTTP API without submitting
the job.
@@ -88,7 +92,7 @@ Run Options:
-preserve-counts
If set, the existing task group counts will be preserved when updating a job.
-consul-token
If set, the passed Consul token is stored in the job before sending to the
Nomad servers. This allows passing the Consul token without storing it in
@@ -127,6 +131,8 @@ func (c *JobRunCommand) AutocompleteFlags() complete.Flags {
"-output": complete.PredictNothing,
"-policy-override": complete.PredictNothing,
"-preserve-counts": complete.PredictNothing,
"-hcl1": complete.PredictNothing,
"-var": complete.PredictAnything,
})
}
@@ -139,6 +145,7 @@ func (c *JobRunCommand) Name() string { return "job run" }
func (c *JobRunCommand) Run(args []string) int {
var detach, verbose, output, override, preserveCounts bool
var checkIndexStr, consulToken, vaultToken, vaultNamespace string
var varArgs cflags.AppendSliceValue
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
@@ -147,10 +154,12 @@ func (c *JobRunCommand) Run(args []string) int {
flags.BoolVar(&output, "output", false, "")
flags.BoolVar(&override, "policy-override", false, "")
flags.BoolVar(&preserveCounts, "preserve-counts", false, "")
flags.BoolVar(&c.JobGetter.hcl1, "hcl1", false, "")
flags.StringVar(&checkIndexStr, "check-index", "", "")
flags.StringVar(&consulToken, "consul-token", "", "")
flags.StringVar(&vaultToken, "vault-token", "", "")
flags.StringVar(&vaultNamespace, "vault-namespace", "", "")
flags.Var(&varArgs, "var", "")
if err := flags.Parse(args); err != nil {
return 1
@@ -171,7 +180,7 @@ func (c *JobRunCommand) Run(args []string) int {
}
// Get Job struct from Jobfile
job, err := c.JobGetter.ApiJob(args[0])
job, err := c.JobGetter.ApiJobWithArgs(args[0], parseVars(varArgs))
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
return 1

View File

@@ -33,7 +33,7 @@ job "job1" {
count = 1
task "task1" {
driver = "exec"
resources = {
resources {
cpu = 1000
memory = 512
}
@@ -127,7 +127,7 @@ job "job1" {
count = 1
task "task1" {
driver = "exec"
resources = {
resources {
cpu = 1000
memory = 512
}
@@ -177,7 +177,7 @@ job "job1" {
count = 1
task "task1" {
driver = "exec"
resources = {
resources {
cpu = 1000
memory = 512
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
cflags "github.com/hashicorp/consul/command/flags"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/command/agent"
@@ -27,6 +28,11 @@ Alias: nomad validate
If the supplied path is "-", the jobfile is read from stdin. Otherwise
it is read from the file at the supplied path or downloaded and
read from URL specified.
Validate Options:
-hcl1
Parses the job file as HCLv1.
`
return strings.TrimSpace(helpText)
}
@@ -36,7 +42,10 @@ func (c *JobValidateCommand) Synopsis() string {
}
func (c *JobValidateCommand) AutocompleteFlags() complete.Flags {
return nil
return complete.Flags{
"-hcl1": complete.PredictNothing,
"-var": complete.PredictAnything,
}
}
func (c *JobValidateCommand) AutocompleteArgs() complete.Predictor {
@@ -46,8 +55,13 @@ func (c *JobValidateCommand) AutocompleteArgs() complete.Predictor {
func (c *JobValidateCommand) Name() string { return "job validate" }
func (c *JobValidateCommand) Run(args []string) int {
var varArgs cflags.AppendSliceValue
flags := c.Meta.FlagSet(c.Name(), FlagSetNone)
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.BoolVar(&c.JobGetter.hcl1, "hcl1", false, "")
flags.Var(&varArgs, "var", "")
if err := flags.Parse(args); err != nil {
return 1
}
@@ -61,7 +75,7 @@ func (c *JobValidateCommand) Run(args []string) int {
}
// Get Job struct from Jobfile
job, err := c.JobGetter.ApiJob(args[0])
job, err := c.JobGetter.ApiJobWithArgs(args[0], parseVars(varArgs))
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting job struct: %s", err))
return 1

View File

@@ -40,7 +40,7 @@ job "job1" {
config {
command = "/bin/sleep"
}
resources = {
resources {
cpu = 1000
memory = 512
}
@@ -142,7 +142,7 @@ job "job1" {
config {
command = "/bin/echo"
}
resources = {
resources {
cpu = 1000
memory = 512
}

View File

@@ -30,7 +30,7 @@ job "snapshot-test-job" {
count = 1
task "task1" {
driver = "exec"
resources = {
resources {
cpu = 1000
memory = 512
}

8
go.mod
View File

@@ -9,6 +9,7 @@ replace (
github.com/godbus/dbus => github.com/godbus/dbus v5.0.1+incompatible
github.com/golang/protobuf => github.com/golang/protobuf v1.3.4
github.com/hashicorp/go-discover => github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f
github.com/hashicorp/hcl => github.com/hashicorp/hcl v1.0.1-0.20201016140508-a07e7d50bbee
github.com/hashicorp/nomad/api => ./api
github.com/kr/pty => github.com/kr/pty v1.1.5
)
@@ -57,6 +58,7 @@ require (
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-connlimit v0.2.0
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840
github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f
github.com/hashicorp/go-envparse v0.0.0-20180119215841-310ca1881b22
github.com/hashicorp/go-getter v1.5.0
@@ -71,8 +73,8 @@ require (
github.com/hashicorp/go-uuid v1.0.1
github.com/hashicorp/go-version v1.2.1-0.20191009193637-2046c9d0f0b0
github.com/hashicorp/golang-lru v0.5.4
github.com/hashicorp/hcl v1.0.1-0.20191016231534-914dc3f8dd7c
github.com/hashicorp/hcl/v2 v2.5.1
github.com/hashicorp/hcl v1.0.1-0.20201016140508-a07e7d50bbee
github.com/hashicorp/hcl/v2 v2.7.1-0.20201020204811-68a97f93bb48
github.com/hashicorp/logutils v1.0.0
github.com/hashicorp/memberlist v0.2.2
github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69
@@ -97,6 +99,7 @@ require (
github.com/mitchellh/go-testing-interface v1.0.3
github.com/mitchellh/hashstructure v1.0.0
github.com/mitchellh/mapstructure v1.3.1
github.com/mitchellh/reflectwalk v1.0.1
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/oklog/run v1.0.1-0.20180308005104-6934b124db28 // indirect
github.com/onsi/gomega v1.9.0 // indirect
@@ -116,6 +119,7 @@ require (
github.com/stretchr/testify v1.6.1
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
github.com/zclconf/go-cty v1.4.1
github.com/zclconf/go-cty-yaml v1.0.2
go.opencensus.io v0.22.1-0.20190713072201-b4a14686f0a9 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 // indirect

21
go.sum
View File

@@ -85,6 +85,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
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=
@@ -117,6 +119,8 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
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/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
@@ -307,6 +311,7 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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=
@@ -355,6 +360,8 @@ github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVo
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-connlimit v0.2.0 h1:OZjcfNxH/hPh/bT2Iw5yOJcLzz+zuIWpsp3I1S4Pjw4=
github.com/hashicorp/go-connlimit v0.2.0/go.mod h1:OUj9FGL1tPIhl/2RCfzYHrIiWj+VVPGNyVPnUX8AqS0=
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840 h1:kgvybwEeu0SXktbB2y3uLHX9lklLo+nzUwh59A3jzQc=
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840/go.mod h1:Abjk0jbRkDaNCzsRhOv2iDCofYpX1eVsjozoiK63qLA=
github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f h1:7WFMVeuJQp6BkzuTv9O52pzwtEFVUJubKYN+zez8eTI=
github.com/hashicorp/go-discover v0.0.0-20200812215701-c4b85f6ed31f/go.mod h1:D4eo8/CN92vm9/9UDG+ldX1/fMFa4kpl8qzyTolus8o=
github.com/hashicorp/go-envparse v0.0.0-20180119215841-310ca1881b22 h1:HTmDIaSN95gbdMyrsbNiXSdW4fbGctGQwEqv0H7OhDQ=
@@ -415,11 +422,10 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl v1.0.1-0.20191016231534-914dc3f8dd7c h1:PdZEHcpa3117kJ1Wa5EYupzCzn9QlBby8Fx2YpZPYvo=
github.com/hashicorp/hcl v1.0.1-0.20191016231534-914dc3f8dd7c/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.5.1 h1:5ytFZykUu2/4U59ogd2f+XZdi9+6oC/Tv5WzsH6fIDA=
github.com/hashicorp/hcl/v2 v2.5.1/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
github.com/hashicorp/hcl v1.0.1-0.20201016140508-a07e7d50bbee h1:8B4HqvMUtYSjsGkYjiQGStc9pXffY2J+Z2SPQAj+wMY=
github.com/hashicorp/hcl v1.0.1-0.20201016140508-a07e7d50bbee/go.mod h1:gwlu9+/P9MmKtYrMsHeFRZPXj2CTPm11TDnMeaRHS7g=
github.com/hashicorp/hcl/v2 v2.7.1-0.20201020204811-68a97f93bb48 h1:iaau0VStfX9CgOlpbceawI94uVEM3sliqnjpHSVQqUo=
github.com/hashicorp/hcl/v2 v2.7.1-0.20201020204811-68a97f93bb48/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5 h1:uk280DXEbQiCOZgCOI3elFSeNxf8YIZiNsbr2pQLYD0=
github.com/hashicorp/hil v0.0.0-20160711231837-1e86c6b523c5/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
@@ -741,9 +747,13 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
github.com/zclconf/go-cty v1.4.0/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
github.com/zclconf/go-cty v1.4.1 h1:Xzr4m4utRDhHDifag1onwwUSq32HLoLBsp+w6tD0880=
github.com/zclconf/go-cty v1.4.1/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ=
github.com/zclconf/go-cty-yaml v1.0.2 h1:dNyg4QLTrv2IfJpm7Wtxi55ed5gLGOlPrZ6kMd51hY0=
github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -765,6 +775,7 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=

View File

@@ -348,6 +348,7 @@ func parseGatewayProxy(o *ast.ObjectItem) (*api.ConsulGatewayProxy, error) {
if err := hcl.DecodeObject(&bind, listenerListVal); err != nil {
panic(err)
}
bind.Name = listenerName
proxy.EnvoyGatewayBindAddresses[listenerName] = &bind
}
}

View File

@@ -135,9 +135,10 @@ func TestParse(t *testing.T) {
ExtraKeysHCL: nil,
},
"bar": {
Name: "bar",
Type: "csi",
Source: "bar-vol",
Name: "bar",
Type: "csi",
Source: "bar-vol",
ReadOnly: true,
MountOptions: &api.CSIMountOptions{
FSType: "ext4",
},
@@ -1422,8 +1423,8 @@ func TestParse(t *testing.T) {
ConnectTimeout: timeToPtr(3 * time.Second),
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: map[string]*api.ConsulGatewayBindAddress{
"listener1": {Address: "10.0.0.1", Port: 8888},
"listener2": {Address: "10.0.0.2", Port: 8889},
"listener1": {Name: "listener1", Address: "10.0.0.1", Port: 8888},
"listener2": {Name: "listener2", Address: "10.0.0.2", Port: 8889},
},
EnvoyGatewayNoDefaultBind: true,
Config: map[string]interface{}{"foo": "bar"},

View File

@@ -76,8 +76,9 @@ job "binstore-storagelocker" {
}
volume "bar" {
type = "csi"
source = "bar-vol"
type = "csi"
source = "bar-vol"
read_only = true
mount_options {
fs_type = "ext4"

View File

@@ -9,7 +9,7 @@ job "elastic" {
foo = "bar"
b = true
val = 5
f =.1
f = 0.1
}
}
}

View File

@@ -6,7 +6,7 @@ job "sidecar_task_name" {
name = "example"
connect {
sidecar_service = {}
sidecar_service {}
sidecar_task {
name = "my-sidecar"

View File

@@ -7,14 +7,14 @@ job "group_service_proxy_expose" {
sidecar_service {
proxy {
expose {
path = {
path {
path = "/health"
protocol = "http"
local_path_port = 2222
listener_port = "healthcheck"
}
path = {
path {
path = "/metrics"
protocol = "grpc"
local_path_port = 3000

133
jobspec2/functions.go Normal file
View File

@@ -0,0 +1,133 @@
package jobspec2
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"
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, allowFS bool) map[string]function.Function {
funcs := map[string]function.Function{
"abs": stdlib.AbsoluteFunc,
"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,
"distinct": stdlib.DistinctFunc,
"element": stdlib.ElementFunc,
"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,
"pow": stdlib.PowFunc,
"range": stdlib.RangeFunc,
"reverse": stdlib.ReverseFunc,
"replace": stdlib.ReplaceFunc,
"regex_replace": stdlib.RegexReplaceFunc,
"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,
"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,
// filesystem calls
"abspath": guardFS(allowFS, filesystem.AbsPathFunc),
"basename": guardFS(allowFS, filesystem.BasenameFunc),
"dirname": guardFS(allowFS, filesystem.DirnameFunc),
"file": guardFS(allowFS, filesystem.MakeFileFunc(basedir, false)),
"fileexists": guardFS(allowFS, filesystem.MakeFileExistsFunc(basedir)),
"fileset": guardFS(allowFS, filesystem.MakeFileSetFunc(basedir)),
"pathexpand": guardFS(allowFS, filesystem.PathExpandFunc),
}
return funcs
}
func guardFS(allowFS bool, fn function.Function) function.Function {
if allowFS {
return fn
}
spec := &function.Spec{
Params: fn.Params(),
VarParam: fn.VarParam(),
Type: func([]cty.Value) (cty.Type, error) {
return cty.DynamicPseudoType, fmt.Errorf("filesystem function disabled")
},
Impl: func([]cty.Value, cty.Type) (cty.Value, error) {
return cty.DynamicVal, fmt.Errorf("filesystem functions disabled")
},
}
return function.New(spec)
}

281
jobspec2/hcl_conversions.go Normal file
View File

@@ -0,0 +1,281 @@
package jobspec2
import (
"fmt"
"reflect"
"time"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/nomad/api"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)
var hclDecoder *gohcl.Decoder
func init() {
hclDecoder = newHCLDecoder()
hclDecoder.RegisterBlockDecoder(reflect.TypeOf(api.TaskGroup{}), decodeTaskGroup)
}
func newHCLDecoder() *gohcl.Decoder {
decoder := &gohcl.Decoder{}
// time conversion
d := time.Duration(0)
decoder.RegisterExpressionDecoder(reflect.TypeOf(d), decodeDuration)
decoder.RegisterExpressionDecoder(reflect.TypeOf(&d), decodeDuration)
// custom nomad types
decoder.RegisterBlockDecoder(reflect.TypeOf(api.Affinity{}), decodeAffinity)
decoder.RegisterBlockDecoder(reflect.TypeOf(api.Constraint{}), decodeConstraint)
decoder.RegisterBlockDecoder(reflect.TypeOf(jobWrapper{}), decodeJob)
return decoder
}
func decodeDuration(expr hcl.Expression, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
srcVal, diags := expr.Value(ctx)
if srcVal.Type() == cty.String {
dur, err := time.ParseDuration(srcVal.AsString())
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsuitable value type",
Detail: fmt.Sprintf("Unsuitable duration value: %s", err.Error()),
Subject: expr.StartRange().Ptr(),
Context: expr.Range().Ptr(),
})
return diags
}
srcVal = cty.NumberIntVal(int64(dur))
}
if srcVal.Type() != cty.Number {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsuitable value type",
Detail: fmt.Sprintf("Unsuitable value: expected a string but found %s", srcVal.Type()),
Subject: expr.StartRange().Ptr(),
Context: expr.Range().Ptr(),
})
return diags
}
err := gocty.FromCtyValue(srcVal, val)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsuitable value type",
Detail: fmt.Sprintf("Unsuitable value: %s", err.Error()),
Subject: expr.StartRange().Ptr(),
Context: expr.Range().Ptr(),
})
}
return diags
}
var affinitySpec = hcldec.ObjectSpec{
"attribute": &hcldec.AttrSpec{Name: "attribute", Type: cty.String, Required: false},
"value": &hcldec.AttrSpec{Name: "value", Type: cty.String, Required: false},
"operator": &hcldec.AttrSpec{Name: "operator", Type: cty.String, Required: false},
"weight": &hcldec.AttrSpec{Name: "weight", Type: cty.Number, Required: false},
api.ConstraintVersion: &hcldec.AttrSpec{Name: api.ConstraintVersion, Type: cty.String, Required: false},
api.ConstraintSemver: &hcldec.AttrSpec{Name: api.ConstraintSemver, Type: cty.String, Required: false},
api.ConstraintRegex: &hcldec.AttrSpec{Name: api.ConstraintRegex, Type: cty.String, Required: false},
api.ConstraintSetContains: &hcldec.AttrSpec{Name: api.ConstraintSetContains, Type: cty.String, Required: false},
api.ConstraintSetContainsAll: &hcldec.AttrSpec{Name: api.ConstraintSetContainsAll, Type: cty.String, Required: false},
api.ConstraintSetContainsAny: &hcldec.AttrSpec{Name: api.ConstraintSetContainsAny, Type: cty.String, Required: false},
}
func decodeAffinity(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
a := val.(*api.Affinity)
v, diags := hcldec.Decode(body, affinitySpec, ctx)
if len(diags) != 0 {
return diags
}
attr := func(attr string) string {
a := v.GetAttr(attr)
if a.IsNull() {
return ""
}
return a.AsString()
}
a.LTarget = attr("attribute")
a.RTarget = attr("value")
a.Operand = attr("operator")
weight := v.GetAttr("weight")
if !weight.IsNull() {
w, _ := weight.AsBigFloat().Int64()
a.Weight = int8ToPtr(int8(w))
}
// If "version" is provided, set the operand
// to "version" and the value to the "RTarget"
if affinity := attr(api.ConstraintVersion); affinity != "" {
a.Operand = api.ConstraintVersion
a.RTarget = affinity
}
// If "semver" is provided, set the operand
// to "semver" and the value to the "RTarget"
if affinity := attr(api.ConstraintSemver); affinity != "" {
a.Operand = api.ConstraintSemver
a.RTarget = affinity
}
// If "regexp" is provided, set the operand
// to "regexp" and the value to the "RTarget"
if affinity := attr(api.ConstraintRegex); affinity != "" {
a.Operand = api.ConstraintRegex
a.RTarget = affinity
}
// If "set_contains_any" is provided, set the operand
// to "set_contains_any" and the value to the "RTarget"
if affinity := attr(api.ConstraintSetContainsAny); affinity != "" {
a.Operand = api.ConstraintSetContainsAny
a.RTarget = affinity
}
// If "set_contains_all" is provided, set the operand
// to "set_contains_all" and the value to the "RTarget"
if affinity := attr(api.ConstraintSetContainsAll); affinity != "" {
a.Operand = api.ConstraintSetContainsAll
a.RTarget = affinity
}
// set_contains is a synonym of set_contains_all
if affinity := attr(api.ConstraintSetContains); affinity != "" {
a.Operand = api.ConstraintSetContains
a.RTarget = affinity
}
if a.Operand == "" {
a.Operand = "="
}
return diags
}
var constraintSpec = hcldec.ObjectSpec{
"attribute": &hcldec.AttrSpec{Name: "attribute", Type: cty.String, Required: false},
"value": &hcldec.AttrSpec{Name: "value", Type: cty.String, Required: false},
"operator": &hcldec.AttrSpec{Name: "operator", Type: cty.String, Required: false},
api.ConstraintDistinctProperty: &hcldec.AttrSpec{Name: api.ConstraintDistinctProperty, Type: cty.String, Required: false},
api.ConstraintDistinctHosts: &hcldec.AttrSpec{Name: api.ConstraintDistinctHosts, Type: cty.Bool, Required: false},
api.ConstraintRegex: &hcldec.AttrSpec{Name: api.ConstraintRegex, Type: cty.String, Required: false},
api.ConstraintVersion: &hcldec.AttrSpec{Name: api.ConstraintVersion, Type: cty.String, Required: false},
api.ConstraintSemver: &hcldec.AttrSpec{Name: api.ConstraintSemver, Type: cty.String, Required: false},
api.ConstraintSetContains: &hcldec.AttrSpec{Name: api.ConstraintSetContains, Type: cty.String, Required: false},
api.ConstraintSetContainsAll: &hcldec.AttrSpec{Name: api.ConstraintSetContainsAll, Type: cty.String, Required: false},
api.ConstraintSetContainsAny: &hcldec.AttrSpec{Name: api.ConstraintSetContainsAny, Type: cty.String, Required: false},
api.ConstraintAttributeIsSet: &hcldec.AttrSpec{Name: api.ConstraintAttributeIsSet, Type: cty.String, Required: false},
api.ConstraintAttributeIsNotSet: &hcldec.AttrSpec{Name: api.ConstraintAttributeIsNotSet, Type: cty.String, Required: false},
}
func decodeConstraint(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
c := val.(*api.Constraint)
v, diags := hcldec.Decode(body, constraintSpec, ctx)
if len(diags) != 0 {
return diags
}
attr := func(attr string) string {
a := v.GetAttr(attr)
if a.IsNull() {
return ""
}
return a.AsString()
}
c.LTarget = attr("attribute")
c.RTarget = attr("value")
c.Operand = attr("operator")
// If "version" is provided, set the operand
// to "version" and the value to the "RTarget"
if constraint := attr(api.ConstraintVersion); constraint != "" {
c.Operand = api.ConstraintVersion
c.RTarget = constraint
}
// If "semver" is provided, set the operand
// to "semver" and the value to the "RTarget"
if constraint := attr(api.ConstraintSemver); constraint != "" {
c.Operand = api.ConstraintSemver
c.RTarget = constraint
}
// If "regexp" is provided, set the operand
// to "regexp" and the value to the "RTarget"
if constraint := attr(api.ConstraintRegex); constraint != "" {
c.Operand = api.ConstraintRegex
c.RTarget = constraint
}
// If "set_contains" is provided, set the operand
// to "set_contains" and the value to the "RTarget"
if constraint := attr(api.ConstraintSetContains); constraint != "" {
c.Operand = api.ConstraintSetContains
c.RTarget = constraint
}
if d := v.GetAttr(api.ConstraintDistinctHosts); !d.IsNull() && d.True() {
c.Operand = api.ConstraintDistinctHosts
}
if property := attr(api.ConstraintDistinctProperty); property != "" {
c.Operand = api.ConstraintDistinctProperty
c.LTarget = property
}
if c.Operand == "" {
c.Operand = "="
}
return diags
}
func decodeTaskGroup(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
tg := val.(*api.TaskGroup)
tgExtra := struct {
Vault *api.Vault `hcl:"vault,block"`
}{}
extra, _ := gohcl.ImpliedBodySchema(tgExtra)
content, tgBody, diags := body.PartialContent(extra)
if len(diags) != 0 {
return diags
}
for _, b := range content.Blocks {
if b.Type == "vault" {
v := &api.Vault{}
diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, v)...)
tgExtra.Vault = v
}
}
d := newHCLDecoder()
diags = d.DecodeBody(tgBody, ctx, tg)
if tgExtra.Vault != nil {
for _, t := range tg.Tasks {
if t.Vault == nil {
t.Vault = tgExtra.Vault
}
}
}
return diags
}

View File

@@ -0,0 +1,176 @@
package hclutil
import (
"github.com/hashicorp/hcl/v2"
hcls "github.com/hashicorp/hcl/v2/hclsyntax"
)
// BlocksAsAttrs rewrites the hcl.Body so that hcl blocks are treated as
// attributes when schema is unknown.
//
// This conversion is necessary for parsing task driver configs, as they can be
// arbitrary nested without pre-defined schema.
//
// More concretely, it changes the following:
//
// ```
// config {
// meta { ... }
// }
// ```
// to
//
// ```
// config {
// meta = { ... } # <- attribute now
// }
// ```
func BlocksAsAttrs(body hcl.Body) hcl.Body {
if hclb, ok := body.(*hcls.Body); ok {
return &blockAttrs{body: hclb}
}
return body
}
type blockAttrs struct {
body hcl.Body
hiddenAttrs map[string]struct{}
hiddenBlocks map[string]struct{}
}
func (b *blockAttrs) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
bc, diags := b.body.Content(schema)
bc.Blocks = expandBlocks(bc.Blocks)
return bc, diags
}
func (b *blockAttrs) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
bc, remainBody, diags := b.body.PartialContent(schema)
bc.Blocks = expandBlocks(bc.Blocks)
remain := &blockAttrs{
body: remainBody,
hiddenAttrs: map[string]struct{}{},
hiddenBlocks: map[string]struct{}{},
}
for name := range b.hiddenAttrs {
remain.hiddenAttrs[name] = struct{}{}
}
for typeName := range b.hiddenBlocks {
remain.hiddenBlocks[typeName] = struct{}{}
}
for _, attrS := range schema.Attributes {
remain.hiddenAttrs[attrS.Name] = struct{}{}
}
for _, blockS := range schema.Blocks {
remain.hiddenBlocks[blockS.Type] = struct{}{}
}
return bc, remain, diags
}
func (b *blockAttrs) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
body, ok := b.body.(*hcls.Body)
if !ok {
return b.body.JustAttributes()
}
attrs := make(hcl.Attributes)
var diags hcl.Diagnostics
if body.Attributes == nil && len(body.Blocks) == 0 {
return attrs, diags
}
for name, attr := range body.Attributes {
if _, hidden := b.hiddenAttrs[name]; hidden {
continue
}
attrs[name] = attr.AsHCLAttribute()
}
for _, blockS := range body.Blocks {
if _, hidden := b.hiddenBlocks[blockS.Type]; hidden {
continue
}
attrs[blockS.Type] = convertToAttribute(blockS).AsHCLAttribute()
}
return attrs, diags
}
func (b *blockAttrs) MissingItemRange() hcl.Range {
return b.body.MissingItemRange()
}
func expandBlocks(blocks hcl.Blocks) hcl.Blocks {
if len(blocks) == 0 {
return blocks
}
r := make([]*hcl.Block, len(blocks))
for i, b := range blocks {
nb := *b
nb.Body = BlocksAsAttrs(b.Body)
r[i] = &nb
}
return r
}
func convertToAttribute(b *hcls.Block) *hcls.Attribute {
items := []hcls.ObjectConsItem{}
for _, attr := range b.Body.Attributes {
keyExpr := &hcls.ScopeTraversalExpr{
Traversal: hcl.Traversal{
hcl.TraverseRoot{
Name: attr.Name,
SrcRange: attr.NameRange,
},
},
SrcRange: attr.NameRange,
}
key := &hcls.ObjectConsKeyExpr{
Wrapped: keyExpr,
}
items = append(items, hcls.ObjectConsItem{
KeyExpr: key,
ValueExpr: attr.Expr,
})
}
for _, block := range b.Body.Blocks {
keyExpr := &hcls.ScopeTraversalExpr{
Traversal: hcl.Traversal{
hcl.TraverseRoot{
Name: block.Type,
SrcRange: block.TypeRange,
},
},
SrcRange: block.TypeRange,
}
key := &hcls.ObjectConsKeyExpr{
Wrapped: keyExpr,
}
valExpr := convertToAttribute(block).Expr
items = append(items, hcls.ObjectConsItem{
KeyExpr: key,
ValueExpr: valExpr,
})
}
attr := &hcls.Attribute{
Name: b.Type,
NameRange: b.TypeRange,
EqualsRange: b.OpenBraceRange,
SrcRange: b.Body.SrcRange,
Expr: &hcls.ObjectConsExpr{
Items: items,
},
}
return attr
}

109
jobspec2/parse.go Normal file
View File

@@ -0,0 +1,109 @@
package jobspec2
import (
"bytes"
"errors"
"io"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/ext/dynblock"
"github.com/hashicorp/hcl/v2/hclsyntax"
hcljson "github.com/hashicorp/hcl/v2/json"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/jobspec2/hclutil"
"github.com/zclconf/go-cty/cty"
)
func Parse(path string, r io.Reader) (*api.Job, error) {
return ParseWithArgs(path, r, nil, false)
}
func toVars(vars map[string]string) cty.Value {
attrs := make(map[string]cty.Value, len(vars))
for k, v := range vars {
attrs[k] = cty.StringVal(v)
}
return cty.ObjectVal(attrs)
}
func ParseWithArgs(path string, r io.Reader, vars map[string]string, allowFS bool) (*api.Job, error) {
if path == "" {
if f, ok := r.(*os.File); ok {
path = f.Name()
}
}
basedir := filepath.Dir(path)
// Copy the reader into an in-memory buffer first since HCL requires it.
var buf bytes.Buffer
if _, err := io.Copy(&buf, r); err != nil {
return nil, err
}
evalContext := &hcl.EvalContext{
Functions: Functions(basedir, allowFS),
Variables: map[string]cty.Value{
"vars": toVars(vars),
},
UnknownVariable: func(expr string) (cty.Value, error) {
v := "${" + expr + "}"
return cty.StringVal(v), nil
},
}
var result struct {
Job jobWrapper `hcl:"job,block"`
}
err := decode(path, buf.Bytes(), evalContext, &result)
if err != nil {
return nil, err
}
normalizeJob(&result.Job)
return result.Job.Job, nil
}
func decode(filename string, src []byte, ctx *hcl.EvalContext, target interface{}) error {
var file *hcl.File
var diags hcl.Diagnostics
if !isJSON(src) {
file, diags = hclsyntax.ParseConfig(src, filename, hcl.Pos{Line: 1, Column: 1})
} else {
file, diags = hcljson.Parse(src, filename)
}
if diags.HasErrors() {
return diags
}
body := hclutil.BlocksAsAttrs(file.Body)
body = dynblock.Expand(body, ctx)
diags = hclDecoder.DecodeBody(body, ctx, target)
if diags.HasErrors() {
var str strings.Builder
for i, diag := range diags {
if i != 0 {
str.WriteByte('\n')
}
str.WriteString(diag.Error())
}
return errors.New(str.String())
}
diags = append(diags, decodeMapInterfaceType(target, ctx)...)
return nil
}
func isJSON(src []byte) bool {
for _, c := range src {
if c == ' ' {
continue
}
return c == '{'
}
return false
}

169
jobspec2/parse_job.go Normal file
View File

@@ -0,0 +1,169 @@
package jobspec2
import (
"time"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/nomad/api"
)
type jobWrapper struct {
JobID string `hcl:",label"`
Job *api.Job
Extra struct {
Vault *api.Vault `hcl:"vault,block"`
Tasks []*api.Task `hcl:"task,block"`
}
}
func decodeJob(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
m := val.(*jobWrapper)
extra, _ := gohcl.ImpliedBodySchema(m.Extra)
content, job, diags := body.PartialContent(extra)
if len(diags) != 0 {
return diags
}
for _, b := range content.Blocks {
if b.Type == "vault" {
v := &api.Vault{}
diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, v)...)
m.Extra.Vault = v
} else if b.Type == "task" {
t := &api.Task{}
diags = append(diags, hclDecoder.DecodeBody(b.Body, ctx, t)...)
if len(b.Labels) == 1 {
t.Name = b.Labels[0]
m.Extra.Tasks = append(m.Extra.Tasks, t)
}
}
}
m.Job = &api.Job{}
return hclDecoder.DecodeBody(job, ctx, m.Job)
}
func normalizeJob(jw *jobWrapper) {
j := jw.Job
if j.Name == nil {
j.Name = &jw.JobID
}
if j.ID == nil {
j.ID = &jw.JobID
}
if j.Periodic != nil && j.Periodic.Spec != nil {
v := "cron"
j.Periodic.SpecType = &v
}
normalizeVault(jw.Extra.Vault)
if len(jw.Extra.Tasks) != 0 {
alone := make([]*api.TaskGroup, 0, len(jw.Extra.Tasks))
for _, t := range jw.Extra.Tasks {
alone = append(alone, &api.TaskGroup{
Name: &t.Name,
Tasks: []*api.Task{t},
})
}
alone = append(alone, j.TaskGroups...)
j.TaskGroups = alone
}
for _, tg := range j.TaskGroups {
normalizeNetworkPorts(tg.Networks)
for _, t := range tg.Tasks {
if t.Resources != nil {
normalizeNetworkPorts(t.Resources.Networks)
}
normalizeTemplates(t.Templates)
// normalize Vault
normalizeVault(t.Vault)
if t.Vault == nil {
t.Vault = jw.Extra.Vault
}
}
}
}
func normalizeVault(v *api.Vault) {
if v == nil {
return
}
if v.Env == nil {
v.Env = boolToPtr(true)
}
if v.ChangeMode == nil {
v.ChangeMode = stringToPtr("restart")
}
}
func normalizeNetworkPorts(networks []*api.NetworkResource) {
if networks == nil {
return
}
for _, n := range networks {
if len(n.DynamicPorts) == 0 {
continue
}
dynamic := make([]api.Port, 0, len(n.DynamicPorts))
var reserved []api.Port
for _, p := range n.DynamicPorts {
if p.Value > 0 {
reserved = append(reserved, p)
} else {
dynamic = append(dynamic, p)
}
}
if len(dynamic) == 0 {
dynamic = nil
}
n.DynamicPorts = dynamic
n.ReservedPorts = reserved
}
}
func normalizeTemplates(templates []*api.Template) {
if len(templates) == 0 {
return
}
for _, t := range templates {
if t.ChangeMode == nil {
t.ChangeMode = stringToPtr("restart")
}
if t.Perms == nil {
t.Perms = stringToPtr("0644")
}
if t.Splay == nil {
t.Splay = durationToPtr(5 * time.Second)
}
}
}
func int8ToPtr(v int8) *int8 {
return &v
}
func boolToPtr(v bool) *bool {
return &v
}
func stringToPtr(v string) *string {
return &v
}
func durationToPtr(v time.Duration) *time.Duration {
return &v
}

172
jobspec2/parse_map.go Normal file
View File

@@ -0,0 +1,172 @@
package jobspec2
import (
"fmt"
"math"
"math/big"
"reflect"
"github.com/hashicorp/hcl/v2"
"github.com/mitchellh/reflectwalk"
"github.com/zclconf/go-cty/cty"
)
// decodeMapInterfaceType decodes hcl instances of `map[string]interface{}` fields
// of v.
//
// The HCL parser stores the hcl AST as the map values, and decodeMapInterfaceType
// evaluates the AST and converts them to the native golang types.
func decodeMapInterfaceType(v interface{}, ctx *hcl.EvalContext) hcl.Diagnostics {
w := &walker{ctx: ctx}
err := reflectwalk.Walk(v, w)
if err != nil {
w.diags = append(w.diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "unexpected internal error",
Detail: err.Error(),
})
}
return w.diags
}
type walker struct {
ctx *hcl.EvalContext
diags hcl.Diagnostics
}
var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{})
func (w *walker) Map(m reflect.Value) error {
if !m.Type().AssignableTo(mapStringInterfaceType) {
return nil
}
for _, k := range m.MapKeys() {
v := m.MapIndex(k)
if attr, ok := v.Interface().(*hcl.Attribute); ok {
c, diags := decodeInterface(attr.Expr, w.ctx)
w.diags = append(w.diags, diags...)
m.SetMapIndex(k, reflect.ValueOf(c))
}
}
return nil
}
func (w *walker) MapElem(m, k, v reflect.Value) error {
return nil
}
func decodeInterface(expr hcl.Expression, ctx *hcl.EvalContext) (interface{}, hcl.Diagnostics) {
srvVal, diags := expr.Value(ctx)
dst, err := interfaceFromCtyValue(srvVal)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "unsuitable value type",
Detail: fmt.Sprintf("Unsuitable value: %s", err.Error()),
Subject: expr.StartRange().Ptr(),
Context: expr.Range().Ptr(),
})
}
return dst, diags
}
func interfaceFromCtyValue(val cty.Value) (interface{}, error) {
t := val.Type()
if val.IsNull() {
return nil, nil
}
if !val.IsKnown() {
return nil, fmt.Errorf("value is not known")
}
// The caller should've guaranteed that the given val is conformant with
// the given type t, so we'll proceed under that assumption here.
switch {
case t.IsPrimitiveType():
switch t {
case cty.String:
return val.AsString(), nil
case cty.Number:
if val.RawEquals(cty.PositiveInfinity) {
return math.Inf(1), nil
} else if val.RawEquals(cty.NegativeInfinity) {
return math.Inf(-1), nil
} else {
return smallestNumber(val.AsBigFloat()), nil
}
case cty.Bool:
return val.True(), nil
default:
panic("unsupported primitive type")
}
case t.IsListType(), t.IsSetType(), t.IsTupleType():
result := []interface{}{}
it := val.ElementIterator()
for it.Next() {
_, ev := it.Element()
evi, err := interfaceFromCtyValue(ev)
if err != nil {
return nil, err
}
result = append(result, evi)
}
return result, nil
case t.IsMapType():
result := map[string]interface{}{}
it := val.ElementIterator()
for it.Next() {
ek, ev := it.Element()
ekv := ek.AsString()
evv, err := interfaceFromCtyValue(ev)
if err != nil {
return nil, err
}
result[ekv] = evv
}
return []map[string]interface{}{result}, nil
case t.IsObjectType():
result := map[string]interface{}{}
for k := range t.AttributeTypes() {
av := val.GetAttr(k)
avv, err := interfaceFromCtyValue(av)
if err != nil {
return nil, err
}
result[k] = avv
}
return []map[string]interface{}{result}, nil
case t.IsCapsuleType():
rawVal := val.EncapsulatedValue()
return rawVal, nil
default:
// should never happen
return nil, fmt.Errorf("cannot serialize %s", t.FriendlyName())
}
}
func smallestNumber(b *big.Float) interface{} {
if v, acc := b.Int64(); acc == big.Exact {
// check if it fits in int
if int64(int(v)) == v {
return int(v)
}
return v
}
if v, acc := b.Float64(); acc == big.Exact || acc == big.Above {
return v
}
return b
}

130
jobspec2/parse_test.go Normal file
View File

@@ -0,0 +1,130 @@
package jobspec2
import (
"io/ioutil"
"os"
"strings"
"testing"
"github.com/hashicorp/nomad/jobspec"
"github.com/stretchr/testify/require"
)
func TestEquivalentToHCL1(t *testing.T) {
hclSpecDir := "../jobspec/test-fixtures/"
fis, err := ioutil.ReadDir(hclSpecDir)
require.NoError(t, err)
for _, fi := range fis {
name := fi.Name()
t.Run(name, func(t *testing.T) {
f, err := os.Open(hclSpecDir + name)
require.NoError(t, err)
defer f.Close()
job1, err := jobspec.Parse(f)
if err != nil {
t.Skip("file is not parsable in v1")
}
f.Seek(0, 0)
job2, err := Parse(name, f)
require.NoError(t, err)
require.Equal(t, job1, job2)
})
}
}
func TestParse_VarsAndFunctions(t *testing.T) {
hcl := `
job "example" {
datacenters = [for s in ["dc1", "dc2"] : upper(s)]
region = vars.region_var
}
`
out, err := ParseWithArgs("input.hcl", strings.NewReader(hcl), map[string]string{"region_var": "aug"}, true)
require.NoError(t, err)
require.Equal(t, []string{"DC1", "DC2"}, out.Datacenters)
require.NotNil(t, out.Region)
require.Equal(t, "aug", *out.Region)
}
// TestParse_UnknownVariables asserts that unknown variables are left intact for further processing
func TestParse_UnknownVariables(t *testing.T) {
hcl := `
job "example" {
datacenters = [for s in ["dc1", "dc2"] : upper(s)]
region = vars.region_var
meta {
known_var = "${vars.region_var}"
unknown_var = "${UNKNOWN}"
}
}
`
out, err := ParseWithArgs("input.hcl", strings.NewReader(hcl), map[string]string{"region_var": "aug"}, true)
require.NoError(t, err)
meta := map[string]string{
"known_var": "aug",
"unknown_var": "${UNKNOWN}",
}
require.Equal(t, meta, out.Meta)
}
func TestParse_FileOperators(t *testing.T) {
hcl := `
job "example" {
region = file("parse_test.go")
}
`
t.Run("enabled", func(t *testing.T) {
out, err := ParseWithArgs("input.hcl", strings.NewReader(hcl), nil, true)
require.NoError(t, err)
expected, err := ioutil.ReadFile("parse_test.go")
require.NoError(t, err)
require.NotNil(t, out.Region)
require.Equal(t, string(expected), *out.Region)
})
t.Run("disabled", func(t *testing.T) {
_, err := ParseWithArgs("input.hcl", strings.NewReader(hcl), nil, false)
require.Error(t, err)
require.Contains(t, err.Error(), "filesystem function disabled")
})
}
func TestParseDynamic(t *testing.T) {
hcl := `
job "example" {
dynamic "group" {
for_each = ["groupA", "groupB", "groupC"]
labels = [group.value]
content {
task "simple" {
driver = "raw_exec"
}
}
}
}
`
out, err := ParseWithArgs("input.hcl", strings.NewReader(hcl), nil, true)
require.NoError(t, err)
require.Len(t, out.TaskGroups, 3)
require.Equal(t, "groupA", *out.TaskGroups[0].Name)
require.Equal(t, "groupB", *out.TaskGroups[1].Name)
require.Equal(t, "groupC", *out.TaskGroups[2].Name)
}

19
vendor/github.com/apparentlymart/go-cidr/LICENSE generated vendored Normal file
View File

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

218
vendor/github.com/apparentlymart/go-cidr/cidr/cidr.go generated vendored Normal file
View File

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

View File

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

32
vendor/github.com/bmatcuk/doublestar/.gitignore generated vendored Normal file
View File

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

15
vendor/github.com/bmatcuk/doublestar/.travis.yml generated vendored Normal file
View File

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

22
vendor/github.com/bmatcuk/doublestar/LICENSE generated vendored Normal file
View File

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

109
vendor/github.com/bmatcuk/doublestar/README.md generated vendored Normal file
View File

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

476
vendor/github.com/bmatcuk/doublestar/doublestar.go generated vendored Normal file
View File

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

3
vendor/github.com/bmatcuk/doublestar/go.mod generated vendored Normal file
View File

@@ -0,0 +1,3 @@
module github.com/bmatcuk/doublestar
go 1.12

9
vendor/github.com/google/uuid/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,9 @@
language: go
go:
- 1.4.3
- 1.5.3
- tip
script:
- go test -v ./...

10
vendor/github.com/google/uuid/CONTRIBUTING.md generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# How to contribute
We definitely welcome patches and contribution to this project!
### Legal requirements
In order to protect both you and ourselves, you will need to sign the
[Contributor License Agreement](https://cla.developers.google.com/clas).
You may have already signed it for other Google projects.

9
vendor/github.com/google/uuid/CONTRIBUTORS generated vendored Normal file
View File

@@ -0,0 +1,9 @@
Paul Borman <borman@google.com>
bmatsuo
shawnps
theory
jboverfelt
dsymonds
cd1
wallclockbuilder
dansouza

27
vendor/github.com/google/uuid/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2009,2014 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

19
vendor/github.com/google/uuid/README.md generated vendored Normal file
View File

@@ -0,0 +1,19 @@
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
The uuid package generates and inspects UUIDs based on
[RFC 4122](http://tools.ietf.org/html/rfc4122)
and DCE 1.1: Authentication and Security Services.
This package is based on the github.com/pborman/uuid package (previously named
code.google.com/p/go-uuid). It differs from these earlier packages in that
a UUID is a 16 byte array rather than a byte slice. One loss due to this
change is the ability to represent an invalid UUID (vs a NIL UUID).
###### Install
`go get github.com/google/uuid`
###### Documentation
[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid)
Full `go doc` style documentation for the package can be viewed online without
installing this package by using the GoDoc site here:
http://godoc.org/github.com/google/uuid

80
vendor/github.com/google/uuid/dce.go generated vendored Normal file
View File

@@ -0,0 +1,80 @@
// Copyright 2016 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.
package uuid
import (
"encoding/binary"
"fmt"
"os"
)
// A Domain represents a Version 2 domain
type Domain byte
// Domain constants for DCE Security (Version 2) UUIDs.
const (
Person = Domain(0)
Group = Domain(1)
Org = Domain(2)
)
// NewDCESecurity returns a DCE Security (Version 2) UUID.
//
// The domain should be one of Person, Group or Org.
// On a POSIX system the id should be the users UID for the Person
// domain and the users GID for the Group. The meaning of id for
// the domain Org or on non-POSIX systems is site defined.
//
// For a given domain/id pair the same token may be returned for up to
// 7 minutes and 10 seconds.
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
uuid, err := NewUUID()
if err == nil {
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
uuid[9] = byte(domain)
binary.BigEndian.PutUint32(uuid[0:], id)
}
return uuid, err
}
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid.
//
// NewDCESecurity(Person, uint32(os.Getuid()))
func NewDCEPerson() (UUID, error) {
return NewDCESecurity(Person, uint32(os.Getuid()))
}
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid.
//
// NewDCESecurity(Group, uint32(os.Getgid()))
func NewDCEGroup() (UUID, error) {
return NewDCESecurity(Group, uint32(os.Getgid()))
}
// Domain returns the domain for a Version 2 UUID. Domains are only defined
// for Version 2 UUIDs.
func (uuid UUID) Domain() Domain {
return Domain(uuid[9])
}
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
// UUIDs.
func (uuid UUID) ID() uint32 {
return binary.BigEndian.Uint32(uuid[0:4])
}
func (d Domain) String() string {
switch d {
case Person:
return "Person"
case Group:
return "Group"
case Org:
return "Org"
}
return fmt.Sprintf("Domain%d", int(d))
}

12
vendor/github.com/google/uuid/doc.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2016 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.
// Package uuid generates and inspects UUIDs.
//
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
// Services.
//
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
// maps or compared directly.
package uuid

1
vendor/github.com/google/uuid/go.mod generated vendored Normal file
View File

@@ -0,0 +1 @@
module github.com/google/uuid

53
vendor/github.com/google/uuid/hash.go generated vendored Normal file
View File

@@ -0,0 +1,53 @@
// Copyright 2016 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.
package uuid
import (
"crypto/md5"
"crypto/sha1"
"hash"
)
// Well known namespace IDs and UUIDs
var (
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
Nil UUID // empty UUID, all zeros
)
// NewHash returns a new UUID derived from the hash of space concatenated with
// data generated by h. The hash should be at least 16 byte in length. The
// first 16 bytes of the hash are used to form the UUID. The version of the
// UUID will be the lower 4 bits of version. NewHash is used to implement
// NewMD5 and NewSHA1.
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset()
h.Write(space[:])
h.Write(data)
s := h.Sum(nil)
var uuid UUID
copy(uuid[:], s)
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
return uuid
}
// NewMD5 returns a new MD5 (Version 3) UUID based on the
// supplied name space and data. It is the same as calling:
//
// NewHash(md5.New(), space, data, 3)
func NewMD5(space UUID, data []byte) UUID {
return NewHash(md5.New(), space, data, 3)
}
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
// supplied name space and data. It is the same as calling:
//
// NewHash(sha1.New(), space, data, 5)
func NewSHA1(space UUID, data []byte) UUID {
return NewHash(sha1.New(), space, data, 5)
}

37
vendor/github.com/google/uuid/marshal.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
// Copyright 2016 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.
package uuid
import "fmt"
// MarshalText implements encoding.TextMarshaler.
func (uuid UUID) MarshalText() ([]byte, error) {
var js [36]byte
encodeHex(js[:], uuid)
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (uuid *UUID) UnmarshalText(data []byte) error {
id, err := ParseBytes(data)
if err == nil {
*uuid = id
}
return err
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (uuid UUID) MarshalBinary() ([]byte, error) {
return uuid[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (uuid *UUID) UnmarshalBinary(data []byte) error {
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
copy(uuid[:], data)
return nil
}

90
vendor/github.com/google/uuid/node.go generated vendored Normal file
View File

@@ -0,0 +1,90 @@
// Copyright 2016 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.
package uuid
import (
"sync"
)
var (
nodeMu sync.Mutex
ifname string // name of interface being used
nodeID [6]byte // hardware for version 1 UUIDs
zeroID [6]byte // nodeID with only 0's
)
// NodeInterface returns the name of the interface from which the NodeID was
// derived. The interface "user" is returned if the NodeID was set by
// SetNodeID.
func NodeInterface() string {
defer nodeMu.Unlock()
nodeMu.Lock()
return ifname
}
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
// If name is "" then the first usable interface found will be used or a random
// Node ID will be generated. If a named interface cannot be found then false
// is returned.
//
// SetNodeInterface never fails when name is "".
func SetNodeInterface(name string) bool {
defer nodeMu.Unlock()
nodeMu.Lock()
return setNodeInterface(name)
}
func setNodeInterface(name string) bool {
iname, addr := getHardwareInterface(name) // null implementation for js
if iname != "" && addr != nil {
ifname = iname
copy(nodeID[:], addr)
return true
}
// We found no interfaces with a valid hardware address. If name
// does not specify a specific interface generate a random Node ID
// (section 4.1.6)
if name == "" {
ifname = "random"
randomBits(nodeID[:])
return true
}
return false
}
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
// if not already set.
func NodeID() []byte {
defer nodeMu.Unlock()
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
nid := nodeID
return nid[:]
}
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
// of id are used. If id is less than 6 bytes then false is returned and the
// Node ID is not set.
func SetNodeID(id []byte) bool {
if len(id) < 6 {
return false
}
defer nodeMu.Unlock()
nodeMu.Lock()
copy(nodeID[:], id)
ifname = "user"
return true
}
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) NodeID() []byte {
var node [6]byte
copy(node[:], uuid[10:])
return node[:]
}

12
vendor/github.com/google/uuid/node_js.go generated vendored Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2017 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.
// +build js
package uuid
// getHardwareInterface returns nil values for the JS version of the code.
// This remvoves the "net" dependency, because it is not used in the browser.
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
func getHardwareInterface(name string) (string, []byte) { return "", nil }

33
vendor/github.com/google/uuid/node_net.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
// Copyright 2017 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.
// +build !js
package uuid
import "net"
var interfaces []net.Interface // cached list of interfaces
// getHardwareInterface returns the name and hardware address of interface name.
// If name is "" then the name and hardware address of one of the system's
// interfaces is returned. If no interfaces are found (name does not exist or
// there are no interfaces) then "", nil is returned.
//
// Only addresses of at least 6 bytes are returned.
func getHardwareInterface(name string) (string, []byte) {
if interfaces == nil {
var err error
interfaces, err = net.Interfaces()
if err != nil {
return "", nil
}
}
for _, ifs := range interfaces {
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
return ifs.Name, ifs.HardwareAddr
}
}
return "", nil
}

59
vendor/github.com/google/uuid/sql.go generated vendored Normal file
View File

@@ -0,0 +1,59 @@
// Copyright 2016 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.
package uuid
import (
"database/sql/driver"
"fmt"
)
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
// Currently, database types that map to string and []byte are supported. Please
// consult database-specific driver documentation for matching types.
func (uuid *UUID) Scan(src interface{}) error {
switch src := src.(type) {
case nil:
return nil
case string:
// if an empty UUID comes from a table, we return a null UUID
if src == "" {
return nil
}
// see Parse for required string format
u, err := Parse(src)
if err != nil {
return fmt.Errorf("Scan: %v", err)
}
*uuid = u
case []byte:
// if an empty UUID comes from a table, we return a null UUID
if len(src) == 0 {
return nil
}
// assumes a simple slice of bytes if 16 bytes
// otherwise attempts to parse
if len(src) != 16 {
return uuid.Scan(string(src))
}
copy((*uuid)[:], src)
default:
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
}
return nil
}
// Value implements sql.Valuer so that UUIDs can be written to databases
// transparently. Currently, UUIDs map to strings. Please consult
// database-specific driver documentation for matching types.
func (uuid UUID) Value() (driver.Value, error) {
return uuid.String(), nil
}

123
vendor/github.com/google/uuid/time.go generated vendored Normal file
View File

@@ -0,0 +1,123 @@
// Copyright 2016 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.
package uuid
import (
"encoding/binary"
"sync"
"time"
)
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
// 1582.
type Time int64
const (
lillian = 2299160 // Julian day of 15 Oct 1582
unix = 2440587 // Julian day of 1 Jan 1970
epoch = unix - lillian // Days between epochs
g1582 = epoch * 86400 // seconds between epochs
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
)
var (
timeMu sync.Mutex
lasttime uint64 // last time we returned
clockSeq uint16 // clock sequence for this run
timeNow = time.Now // for testing
)
// UnixTime converts t the number of seconds and nanoseconds using the Unix
// epoch of 1 Jan 1970.
func (t Time) UnixTime() (sec, nsec int64) {
sec = int64(t - g1582ns100)
nsec = (sec % 10000000) * 100
sec /= 10000000
return sec, nsec
}
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
// clock sequence as well as adjusting the clock sequence as needed. An error
// is returned if the current time cannot be determined.
func GetTime() (Time, uint16, error) {
defer timeMu.Unlock()
timeMu.Lock()
return getTime()
}
func getTime() (Time, uint16, error) {
t := timeNow()
// If we don't have a clock sequence already, set one.
if clockSeq == 0 {
setClockSequence(-1)
}
now := uint64(t.UnixNano()/100) + g1582ns100
// If time has gone backwards with this clock sequence then we
// increment the clock sequence
if now <= lasttime {
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
}
lasttime = now
return Time(now), clockSeq, nil
}
// ClockSequence returns the current clock sequence, generating one if not
// already set. The clock sequence is only used for Version 1 UUIDs.
//
// The uuid package does not use global static storage for the clock sequence or
// the last time a UUID was generated. Unless SetClockSequence is used, a new
// random clock sequence is generated the first time a clock sequence is
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
func ClockSequence() int {
defer timeMu.Unlock()
timeMu.Lock()
return clockSequence()
}
func clockSequence() int {
if clockSeq == 0 {
setClockSequence(-1)
}
return int(clockSeq & 0x3fff)
}
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
// -1 causes a new sequence to be generated.
func SetClockSequence(seq int) {
defer timeMu.Unlock()
timeMu.Lock()
setClockSequence(seq)
}
func setClockSequence(seq int) {
if seq == -1 {
var b [2]byte
randomBits(b[:]) // clock sequence
seq = int(b[0])<<8 | int(b[1])
}
oldSeq := clockSeq
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
if oldSeq != clockSeq {
lasttime = 0
}
}
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
// uuid. The time is only defined for version 1 and 2 UUIDs.
func (uuid UUID) Time() Time {
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
return Time(time)
}
// ClockSequence returns the clock sequence encoded in uuid.
// The clock sequence is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) ClockSequence() int {
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
}

43
vendor/github.com/google/uuid/util.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
// Copyright 2016 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.
package uuid
import (
"io"
)
// randomBits completely fills slice b with random data.
func randomBits(b []byte) {
if _, err := io.ReadFull(rander, b); err != nil {
panic(err.Error()) // rand should never fail
}
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts hex characters x1 and x2 into a byte.
func xtob(x1, x2 byte) (byte, bool) {
b1 := xvalues[x1]
b2 := xvalues[x2]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}

245
vendor/github.com/google/uuid/uuid.go generated vendored Normal file
View File

@@ -0,0 +1,245 @@
// 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.
package uuid
import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
)
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
// 4122.
type UUID [16]byte
// A Version represents a UUID's version.
type Version byte
// A Variant represents a UUID's variant.
type Variant byte
// Constants returned by Variant.
const (
Invalid = Variant(iota) // Invalid UUID
RFC4122 // The variant specified in RFC4122
Reserved // Reserved, NCS backward compatibility.
Microsoft // Reserved, Microsoft Corporation backward compatibility.
Future // Reserved for future definition.
)
var rander = rand.Reader // random function
// 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
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")
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
v, ok := xtob(s[x], s[x+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
uuid[i] = v
}
return uuid, nil
}
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
func ParseBytes(b []byte) (UUID, error) {
var uuid UUID
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")
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
v, ok := xtob(b[x], b[x+1])
if !ok {
return uuid, errors.New("invalid UUID format")
}
uuid[i] = v
}
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) {
err = uuid.UnmarshalBinary(b)
return uuid, err
}
// Must returns uuid if err is nil and panics otherwise.
func Must(uuid UUID, err error) UUID {
if err != nil {
panic(err)
}
return uuid
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
var buf [36]byte
encodeHex(buf[:], uuid)
return string(buf[:])
}
// URN returns the RFC 2141 URN form of uuid,
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
func (uuid UUID) URN() string {
var buf [36 + 9]byte
copy(buf[:], "urn:uuid:")
encodeHex(buf[9:], uuid)
return string(buf[:])
}
func encodeHex(dst []byte, uuid UUID) {
hex.Encode(dst, uuid[:4])
dst[8] = '-'
hex.Encode(dst[9:13], uuid[4:6])
dst[13] = '-'
hex.Encode(dst[14:18], uuid[6:8])
dst[18] = '-'
hex.Encode(dst[19:23], uuid[8:10])
dst[23] = '-'
hex.Encode(dst[24:], uuid[10:])
}
// Variant returns the variant encoded in uuid.
func (uuid UUID) Variant() Variant {
switch {
case (uuid[8] & 0xc0) == 0x80:
return RFC4122
case (uuid[8] & 0xe0) == 0xc0:
return Microsoft
case (uuid[8] & 0xe0) == 0xe0:
return Future
default:
return Reserved
}
}
// Version returns the version of uuid.
func (uuid UUID) Version() Version {
return Version(uuid[6] >> 4)
}
func (v Version) String() string {
if v > 15 {
return fmt.Sprintf("BAD_VERSION_%d", v)
}
return fmt.Sprintf("VERSION_%d", v)
}
func (v Variant) String() string {
switch v {
case RFC4122:
return "RFC4122"
case Reserved:
return "Reserved"
case Microsoft:
return "Microsoft"
case Future:
return "Future"
case Invalid:
return "Invalid"
}
return fmt.Sprintf("BadVariant%d", int(v))
}
// SetRand sets the random number generator to r, which implements io.Reader.
// If r.Read returns an error when the package requests random data then
// a panic will be issued.
//
// Calling SetRand with nil sets the random number generator to the default
// generator.
func SetRand(r io.Reader) {
if r == nil {
rander = rand.Reader
return
}
rander = r
}

44
vendor/github.com/google/uuid/version1.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2016 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.
package uuid
import (
"encoding/binary"
)
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns nil and an error.
//
// In most cases, New should be used.
func NewUUID() (UUID, error) {
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
nodeMu.Unlock()
var uuid UUID
now, seq, err := GetTime()
if err != nil {
return uuid, err
}
timeLow := uint32(now & 0xffffffff)
timeMid := uint16((now >> 32) & 0xffff)
timeHi := uint16((now >> 48) & 0x0fff)
timeHi |= 0x1000 // Version 1
binary.BigEndian.PutUint32(uuid[0:], timeLow)
binary.BigEndian.PutUint16(uuid[4:], timeMid)
binary.BigEndian.PutUint16(uuid[6:], timeHi)
binary.BigEndian.PutUint16(uuid[8:], seq)
copy(uuid[10:], nodeID[:])
return uuid, nil
}

38
vendor/github.com/google/uuid/version4.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2016 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.
package uuid
import "io"
// New creates a new random UUID or panics. New is equivalent to
// the expression
//
// uuid.Must(uuid.NewRandom())
func New() UUID {
return Must(NewRandom())
}
// NewRandom returns a Random (Version 4) UUID.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 1011),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
var uuid UUID
_, err := io.ReadFull(rander, uuid[:])
if err != nil {
return Nil, err
}
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}

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

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,9 +1,9 @@
y.output
# ignore intellij files
.idea
*.iml
*.ipr
*.iws
*.test
y.output
# ignore intellij files
.idea
*.iml
*.ipr
*.iws
*.test

View File

@@ -1,13 +0,0 @@
sudo: false
language: go
go:
- 1.x
- tip
branches:
only:
- master
script: make test

View File

@@ -1,18 +1,18 @@
TEST?=./...
default: test
fmt: generate
go fmt ./...
test: generate
go get -t ./...
go test $(TEST) $(TESTARGS)
generate:
go generate ./...
updatedeps:
go get -u golang.org/x/tools/cmd/stringer
.PHONY: default generate test updatedeps
TEST?=./...
default: test
fmt: generate
go fmt ./...
test: generate
go get -t ./...
go test $(TEST) $(TESTARGS)
generate:
go generate ./...
updatedeps:
go get -u golang.org/x/tools/cmd/stringer
.PHONY: default generate test updatedeps

View File

@@ -1,19 +0,0 @@
version: "build-{branch}-{build}"
image: Visual Studio 2015
clone_folder: c:\gopath\src\github.com\hashicorp\hcl
environment:
GOPATH: c:\gopath
init:
- git config --global core.autocrlf false
install:
- cmd: >-
echo %Path%
go version
go env
go get -t ./...
build_script:
- cmd: go test -v ./...

View File

@@ -13,9 +13,6 @@ import (
"github.com/hashicorp/hcl/hcl/token"
)
// This is the tag to use with structures to have settings for HCL
const tagName = "hcl"
var (
// nodeType holds a reference to the type of ast.Node
nodeType reflect.Type = findNodeType()
@@ -597,7 +594,7 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value)
structType := structVal.Type()
for i := 0; i < structType.NumField(); i++ {
fieldType := structType.Field(i)
tagParts := strings.Split(fieldType.Tag.Get(tagName), ",")
tagParts := strings.Split(fieldTag(fieldType), ",")
// Ignore fields with tag name "-"
if tagParts[0] == "-" {
@@ -664,7 +661,7 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value)
fieldName := field.Name
tagValue := field.Tag.Get(tagName)
tagValue := fieldTag(field)
tagParts := strings.SplitN(tagValue, ",", 2)
if len(tagParts) >= 2 {
switch tagParts[1] {
@@ -765,3 +762,12 @@ func removeCaseFold(xs []string, y string) []string {
}
return xs
}
// read the tag for HCL settings: check `hcl1` first and fallback to `hcl`
func fieldTag(fieldType reflect.StructField) string {
tag := fieldType.Tag.Get("hcl1")
if tag == "" {
tag = fieldType.Tag.Get("hcl")
}
return tag
}

View File

@@ -1,3 +1,5 @@
module github.com/hashicorp/hcl
require github.com/davecgh/go-spew v1.1.1
go 1.14

View File

@@ -25,6 +25,8 @@ func (ObjectType) node() {}
func (LiteralType) node() {}
func (ListType) node() {}
var unknownPos token.Pos
// File represents a single HCL file
type File struct {
Node Node // usually a *ObjectList
@@ -108,7 +110,12 @@ func (o *ObjectList) Elem() *ObjectList {
}
func (o *ObjectList) Pos() token.Pos {
// always returns the uninitiliazed position
// If an Object has no members, it won't have a first item
// to use as position
if len(o.Items) == 0 {
return unknownPos
}
// Return the uninitialized position
return o.Items[0].Pos()
}
@@ -133,10 +140,10 @@ type ObjectItem struct {
}
func (o *ObjectItem) Pos() token.Pos {
// I'm not entirely sure what causes this, but removing this causes
// a test failure. We should investigate at some point.
// If a parsed object has no keys, there is no position
// for its first element.
if len(o.Keys) == 0 {
return token.Pos{}
return unknownPos
}
return o.Keys[0].Pos()

View File

@@ -1,5 +1,29 @@
# HCL Changelog
## 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
* hcldec: Add a new `Spec`, `ValidateSpec`, which allows custom validation of values at decode-time. ([#387](https://github.com/hashicorp/hcl/pull/387))
### Bugs Fixed
* hclsyntax: Fix panic with combination of sequences and null arguments ([#386](https://github.com/hashicorp/hcl/pull/386))
* hclsyntax: Fix handling of unknown values and sequences ([#386](https://github.com/hashicorp/hcl/pull/386))
## v2.5.1 (May 14, 2020)
### Bugs Fixed

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

@@ -22,14 +22,14 @@ const (
)
// Diagnostic represents information to be presented to a user about an
// error or anomoly in parsing or evaluating configuration.
// error or anomaly in parsing or evaluating configuration.
type Diagnostic struct {
Severity DiagnosticSeverity
// Summary and Detail contain the English-language description of the
// problem. Summary is a terse description of the general problem and
// detail is a more elaborate, often-multi-sentence description of
// the probem and what might be done to solve it.
// the problem and what might be done to solve it.
Summary string
Detail string

View File

@@ -11,6 +11,8 @@ type EvalContext struct {
Variables map[string]cty.Value
Functions map[string]function.Function
parent *EvalContext
UnknownVariable func(expr string) (cty.Value, error)
}
// NewChild returns a new EvalContext that is a child of the receiver.

View File

@@ -0,0 +1,184 @@
# HCL Dynamic Blocks Extension
This HCL extension implements a special block type named "dynamic" that can
be used to dynamically generate blocks of other types by iterating over
collection values.
Normally the block structure in an HCL configuration file is rigid, even
though dynamic expressions can be used within attribute values. This is
convenient for most applications since it allows the overall structure of
the document to be decoded easily, but in some applications it is desirable
to allow dynamic block generation within certain portions of the configuration.
Dynamic block generation is performed using the `dynamic` block type:
```hcl
toplevel {
nested {
foo = "static block 1"
}
dynamic "nested" {
for_each = ["a", "b", "c"]
iterator = nested
content {
foo = "dynamic block ${nested.value}"
}
}
nested {
foo = "static block 2"
}
}
```
The above is interpreted as if it were written as follows:
```hcl
toplevel {
nested {
foo = "static block 1"
}
nested {
foo = "dynamic block a"
}
nested {
foo = "dynamic block b"
}
nested {
foo = "dynamic block c"
}
nested {
foo = "static block 2"
}
}
```
Since HCL block syntax is not normally exposed to the possibility of unknown
values, this extension must make some compromises when asked to iterate over
an unknown collection. If the length of the collection cannot be statically
recognized (because it is an unknown value of list, map, or set type) then
the `dynamic` construct will generate a _single_ dynamic block whose iterator
key and value are both unknown values of the dynamic pseudo-type, thus causing
any attribute values derived from iteration to appear as unknown values. There
is no explicit representation of the fact that the length of the collection may
eventually be different than one.
## Usage
Pass a body to function `Expand` to obtain a new body that will, on access
to its content, evaluate and expand any nested `dynamic` blocks.
Dynamic block processing is also automatically propagated into any nested
blocks that are returned, allowing users to nest dynamic blocks inside
one another and to nest dynamic blocks inside other static blocks.
HCL structural decoding does not normally have access to an `EvalContext`, so
any variables and functions that should be available to the `for_each`
and `labels` expressions must be passed in when calling `Expand`. Expressions
within the `content` block are evaluated separately and so can be passed a
separate `EvalContext` if desired, during normal attribute expression
evaluation.
## Detecting Variables
Some applications dynamically generate an `EvalContext` by analyzing which
variables are referenced by an expression before evaluating it.
This unfortunately requires some extra effort when this analysis is required
for the context passed to `Expand`: the HCL API requires a schema to be
provided in order to do any analysis of the blocks in a body, but the low-level
schema model provides a description of only one level of nested blocks at
a time, and thus a new schema must be provided for each additional level of
nesting.
To make this arduous process as convenient as possible, this package provides
a helper function `WalkForEachVariables`, which returns a `WalkVariablesNode`
instance that can be used to find variables directly in a given body and also
determine which nested blocks require recursive calls. Using this mechanism
requires that the caller be able to look up a schema given a nested block type.
For _simple_ formats where a specific block type name always has the same schema
regardless of context, a walk can be implemented as follows:
```go
func walkVariables(node dynblock.WalkVariablesNode, schema *hcl.BodySchema) []hcl.Traversal {
vars, children := node.Visit(schema)
for _, child := range children {
var childSchema *hcl.BodySchema
switch child.BlockTypeName {
case "a":
childSchema = &hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "b",
LabelNames: []string{"key"},
},
},
}
case "b":
childSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "val",
Required: true,
},
},
}
default:
// Should never happen, because the above cases should be exhaustive
// for the application's configuration format.
panic(fmt.Errorf("can't find schema for unknown block type %q", child.BlockTypeName))
}
vars = append(vars, testWalkAndAccumVars(child.Node, childSchema)...)
}
}
```
### Detecting Variables with `hcldec` Specifications
For applications that use the higher-level `hcldec` package to decode nested
configuration structures into `cty` values, the same specification can be used
to automatically drive the recursive variable-detection walk described above.
The helper function `ForEachVariablesHCLDec` allows an entire recursive
configuration structure to be analyzed in a single call given a `hcldec.Spec`
that describes the nested block structure. This means a `hcldec`-based
application can support dynamic blocks with only a little additional effort:
```go
func decodeBody(body hcl.Body, spec hcldec.Spec) (cty.Value, hcl.Diagnostics) {
// Determine which variables are needed to expand dynamic blocks
neededForDynamic := dynblock.ForEachVariablesHCLDec(body, spec)
// Build a suitable EvalContext and expand dynamic blocks
dynCtx := buildEvalContext(neededForDynamic)
dynBody := dynblock.Expand(body, dynCtx)
// Determine which variables are needed to fully decode the expanded body
// This will analyze expressions that came both from static blocks in the
// original body and from blocks that were dynamically added by Expand.
neededForDecode := hcldec.Variables(dynBody, spec)
// Build a suitable EvalContext and then fully decode the body as per the
// hcldec specification.
decCtx := buildEvalContext(neededForDecode)
return hcldec.Decode(dynBody, spec, decCtx)
}
func buildEvalContext(needed []hcl.Traversal) *hcl.EvalContext {
// (to be implemented by your application)
}
```
# Performance
This extension is going quite harshly against the grain of the HCL API, and
so it uses lots of wrapping objects and temporary data structures to get its
work done. HCL in general is not suitable for use in high-performance situations
or situations sensitive to memory pressure, but that is _especially_ true for
this extension.

View File

@@ -0,0 +1,262 @@
package dynblock
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)
// expandBody wraps another hcl.Body and expands any "dynamic" blocks found
// inside whenever Content or PartialContent is called.
type expandBody struct {
original hcl.Body
forEachCtx *hcl.EvalContext
iteration *iteration // non-nil if we're nested inside another "dynamic" block
// These are used with PartialContent to produce a "remaining items"
// body to return. They are nil on all bodies fresh out of the transformer.
//
// Note that this is re-implemented here rather than delegating to the
// existing support required by the underlying body because we need to
// retain access to the entire original body on subsequent decode operations
// so we can retain any "dynamic" blocks for types we didn't take consume
// on the first pass.
hiddenAttrs map[string]struct{}
hiddenBlocks map[string]hcl.BlockHeaderSchema
}
func (b *expandBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
extSchema := b.extendSchema(schema)
rawContent, diags := b.original.Content(extSchema)
blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, false)
diags = append(diags, blockDiags...)
attrs := b.prepareAttributes(rawContent.Attributes)
content := &hcl.BodyContent{
Attributes: attrs,
Blocks: blocks,
MissingItemRange: b.original.MissingItemRange(),
}
return content, diags
}
func (b *expandBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
extSchema := b.extendSchema(schema)
rawContent, _, diags := b.original.PartialContent(extSchema)
// We discard the "remain" argument above because we're going to construct
// our own remain that also takes into account remaining "dynamic" blocks.
blocks, blockDiags := b.expandBlocks(schema, rawContent.Blocks, true)
diags = append(diags, blockDiags...)
attrs := b.prepareAttributes(rawContent.Attributes)
content := &hcl.BodyContent{
Attributes: attrs,
Blocks: blocks,
MissingItemRange: b.original.MissingItemRange(),
}
remain := &expandBody{
original: b.original,
forEachCtx: b.forEachCtx,
iteration: b.iteration,
hiddenAttrs: make(map[string]struct{}),
hiddenBlocks: make(map[string]hcl.BlockHeaderSchema),
}
for name := range b.hiddenAttrs {
remain.hiddenAttrs[name] = struct{}{}
}
for typeName, blockS := range b.hiddenBlocks {
remain.hiddenBlocks[typeName] = blockS
}
for _, attrS := range schema.Attributes {
remain.hiddenAttrs[attrS.Name] = struct{}{}
}
for _, blockS := range schema.Blocks {
remain.hiddenBlocks[blockS.Type] = blockS
}
return content, remain, diags
}
func (b *expandBody) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema {
// We augment the requested schema to also include our special "dynamic"
// block type, since then we'll get instances of it interleaved with
// all of the literal child blocks we must also include.
extSchema := &hcl.BodySchema{
Attributes: schema.Attributes,
Blocks: make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+len(b.hiddenBlocks)+1),
}
copy(extSchema.Blocks, schema.Blocks)
extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema)
// If we have any hiddenBlocks then we also need to register those here
// so that a call to "Content" on the underlying body won't fail.
// (We'll filter these out again once we process the result of either
// Content or PartialContent.)
for _, blockS := range b.hiddenBlocks {
extSchema.Blocks = append(extSchema.Blocks, blockS)
}
// If we have any hiddenAttrs then we also need to register these, for
// the same reason as we deal with hiddenBlocks above.
if len(b.hiddenAttrs) != 0 {
newAttrs := make([]hcl.AttributeSchema, len(schema.Attributes), len(schema.Attributes)+len(b.hiddenAttrs))
copy(newAttrs, extSchema.Attributes)
for name := range b.hiddenAttrs {
newAttrs = append(newAttrs, hcl.AttributeSchema{
Name: name,
Required: false,
})
}
extSchema.Attributes = newAttrs
}
return extSchema
}
func (b *expandBody) prepareAttributes(rawAttrs hcl.Attributes) hcl.Attributes {
if len(b.hiddenAttrs) == 0 && b.iteration == nil {
// Easy path: just pass through the attrs from the original body verbatim
return rawAttrs
}
// Otherwise we have some work to do: we must filter out any attributes
// that are hidden (since a previous PartialContent call already saw these)
// and wrap the expressions of the inner attributes so that they will
// have access to our iteration variables.
attrs := make(hcl.Attributes, len(rawAttrs))
for name, rawAttr := range rawAttrs {
if _, hidden := b.hiddenAttrs[name]; hidden {
continue
}
if b.iteration != nil {
attr := *rawAttr // shallow copy so we can mutate it
attr.Expr = exprWrap{
Expression: attr.Expr,
i: b.iteration,
}
attrs[name] = &attr
} else {
// If we have no active iteration then no wrapping is required.
attrs[name] = rawAttr
}
}
return attrs
}
func (b *expandBody) expandBlocks(schema *hcl.BodySchema, rawBlocks hcl.Blocks, partial bool) (hcl.Blocks, hcl.Diagnostics) {
var blocks hcl.Blocks
var diags hcl.Diagnostics
for _, rawBlock := range rawBlocks {
switch rawBlock.Type {
case "dynamic":
realBlockType := rawBlock.Labels[0]
if _, hidden := b.hiddenBlocks[realBlockType]; hidden {
continue
}
var blockS *hcl.BlockHeaderSchema
for _, candidate := range schema.Blocks {
if candidate.Type == realBlockType {
blockS = &candidate
break
}
}
if blockS == nil {
// Not a block type that the caller requested.
if !partial {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsupported block type",
Detail: fmt.Sprintf("Blocks of type %q are not expected here.", realBlockType),
Subject: &rawBlock.LabelRanges[0],
})
}
continue
}
spec, specDiags := b.decodeSpec(blockS, rawBlock)
diags = append(diags, specDiags...)
if specDiags.HasErrors() {
continue
}
if spec.forEachVal.IsKnown() {
for it := spec.forEachVal.ElementIterator(); it.Next(); {
key, value := it.Element()
i := b.iteration.MakeChild(spec.iteratorName, key, value)
block, blockDiags := spec.newBlock(i, b.forEachCtx)
diags = append(diags, blockDiags...)
if block != nil {
// Attach our new iteration context so that attributes
// and other nested blocks can refer to our iterator.
block.Body = b.expandChild(block.Body, i)
blocks = append(blocks, block)
}
}
} else {
// If our top-level iteration value isn't known then we're forced
// to compromise since HCL doesn't have any concept of an
// "unknown block". In this case then, we'll produce a single
// dynamic block with the iterator values set to DynamicVal,
// which at least makes the potential for a block visible
// in our result, even though it's not represented in a fully-accurate
// way.
i := b.iteration.MakeChild(spec.iteratorName, cty.DynamicVal, cty.DynamicVal)
block, blockDiags := spec.newBlock(i, b.forEachCtx)
diags = append(diags, blockDiags...)
if block != nil {
block.Body = b.expandChild(block.Body, i)
// We additionally force all of the leaf attribute values
// in the result to be unknown so the calling application
// can, if necessary, use that as a heuristic to detect
// when a single nested block might be standing in for
// multiple blocks yet to be expanded. This retains the
// structure of the generated body but forces all of its
// leaf attribute values to be unknown.
block.Body = unknownBody{block.Body}
blocks = append(blocks, block)
}
}
default:
if _, hidden := b.hiddenBlocks[rawBlock.Type]; !hidden {
// A static block doesn't create a new iteration context, but
// it does need to inherit _our own_ iteration context in
// case it contains expressions that refer to our inherited
// iterators, or nested "dynamic" blocks.
expandedBlock := *rawBlock // shallow copy
expandedBlock.Body = b.expandChild(rawBlock.Body, b.iteration)
blocks = append(blocks, &expandedBlock)
}
}
}
return blocks, diags
}
func (b *expandBody) expandChild(child hcl.Body, i *iteration) hcl.Body {
chiCtx := i.EvalContext(b.forEachCtx)
ret := Expand(child, chiCtx)
ret.(*expandBody).iteration = i
return ret
}
func (b *expandBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
// blocks aren't allowed in JustAttributes mode and this body can
// only produce blocks, so we'll just pass straight through to our
// underlying body here.
return b.original.JustAttributes()
}
func (b *expandBody) MissingItemRange() hcl.Range {
return b.original.MissingItemRange()
}

View File

@@ -0,0 +1,215 @@
package dynblock
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"
)
type expandSpec struct {
blockType string
blockTypeRange hcl.Range
defRange hcl.Range
forEachVal cty.Value
iteratorName string
labelExprs []hcl.Expression
contentBody hcl.Body
inherited map[string]*iteration
}
func (b *expandBody) decodeSpec(blockS *hcl.BlockHeaderSchema, rawSpec *hcl.Block) (*expandSpec, hcl.Diagnostics) {
var diags hcl.Diagnostics
var schema *hcl.BodySchema
if len(blockS.LabelNames) != 0 {
schema = dynamicBlockBodySchemaLabels
} else {
schema = dynamicBlockBodySchemaNoLabels
}
specContent, specDiags := rawSpec.Body.Content(schema)
diags = append(diags, specDiags...)
if specDiags.HasErrors() {
return nil, diags
}
//// for_each attribute
eachAttr := specContent.Attributes["for_each"]
eachVal, eachDiags := eachAttr.Expr.Value(b.forEachCtx)
diags = append(diags, eachDiags...)
if !eachVal.CanIterateElements() && eachVal.Type() != cty.DynamicPseudoType {
// We skip this error for DynamicPseudoType because that means we either
// have a null (which is checked immediately below) or an unknown
// (which is handled in the expandBody Content methods).
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid dynamic for_each value",
Detail: fmt.Sprintf("Cannot use a %s value in for_each. An iterable collection is required.", eachVal.Type().FriendlyName()),
Subject: eachAttr.Expr.Range().Ptr(),
Expression: eachAttr.Expr,
EvalContext: b.forEachCtx,
})
return nil, diags
}
if eachVal.IsNull() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid dynamic for_each value",
Detail: "Cannot use a null value in for_each.",
Subject: eachAttr.Expr.Range().Ptr(),
Expression: eachAttr.Expr,
EvalContext: b.forEachCtx,
})
return nil, diags
}
//// iterator attribute
iteratorName := blockS.Type
if iteratorAttr := specContent.Attributes["iterator"]; iteratorAttr != nil {
itTraversal, itDiags := hcl.AbsTraversalForExpr(iteratorAttr.Expr)
diags = append(diags, itDiags...)
if itDiags.HasErrors() {
return nil, diags
}
if len(itTraversal) != 1 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid dynamic iterator name",
Detail: "Dynamic iterator must be a single variable name.",
Subject: itTraversal.SourceRange().Ptr(),
})
return nil, diags
}
iteratorName = itTraversal.RootName()
}
var labelExprs []hcl.Expression
if labelsAttr := specContent.Attributes["labels"]; labelsAttr != nil {
var labelDiags hcl.Diagnostics
labelExprs, labelDiags = hcl.ExprList(labelsAttr.Expr)
diags = append(diags, labelDiags...)
if labelDiags.HasErrors() {
return nil, diags
}
if len(labelExprs) > len(blockS.LabelNames) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Extraneous dynamic block label",
Detail: fmt.Sprintf("Blocks of type %q require %d label(s).", blockS.Type, len(blockS.LabelNames)),
Subject: labelExprs[len(blockS.LabelNames)].Range().Ptr(),
})
return nil, diags
} else if len(labelExprs) < len(blockS.LabelNames) {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Insufficient dynamic block labels",
Detail: fmt.Sprintf("Blocks of type %q require %d label(s).", blockS.Type, len(blockS.LabelNames)),
Subject: labelsAttr.Expr.Range().Ptr(),
})
return nil, diags
}
}
// Since our schema requests only blocks of type "content", we can assume
// that all entries in specContent.Blocks are content blocks.
if len(specContent.Blocks) == 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing dynamic content block",
Detail: "A dynamic block must have a nested block of type \"content\" to describe the body of each generated block.",
Subject: &specContent.MissingItemRange,
})
return nil, diags
}
if len(specContent.Blocks) > 1 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Extraneous dynamic content block",
Detail: "Only one nested content block is allowed for each dynamic block.",
Subject: &specContent.Blocks[1].DefRange,
})
return nil, diags
}
return &expandSpec{
blockType: blockS.Type,
blockTypeRange: rawSpec.LabelRanges[0],
defRange: rawSpec.DefRange,
forEachVal: eachVal,
iteratorName: iteratorName,
labelExprs: labelExprs,
contentBody: specContent.Blocks[0].Body,
}, diags
}
func (s *expandSpec) newBlock(i *iteration, ctx *hcl.EvalContext) (*hcl.Block, hcl.Diagnostics) {
var diags hcl.Diagnostics
var labels []string
var labelRanges []hcl.Range
lCtx := i.EvalContext(ctx)
for _, labelExpr := range s.labelExprs {
labelVal, labelDiags := labelExpr.Value(lCtx)
diags = append(diags, labelDiags...)
if labelDiags.HasErrors() {
return nil, diags
}
var convErr error
labelVal, convErr = convert.Convert(labelVal, cty.String)
if convErr != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid dynamic block label",
Detail: fmt.Sprintf("Cannot use this value as a dynamic block label: %s.", convErr),
Subject: labelExpr.Range().Ptr(),
Expression: labelExpr,
EvalContext: lCtx,
})
return nil, diags
}
if labelVal.IsNull() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid dynamic block label",
Detail: "Cannot use a null value as a dynamic block label.",
Subject: labelExpr.Range().Ptr(),
Expression: labelExpr,
EvalContext: lCtx,
})
return nil, diags
}
if !labelVal.IsKnown() {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid dynamic block label",
Detail: "This value is not yet known. Dynamic block labels must be immediately-known values.",
Subject: labelExpr.Range().Ptr(),
Expression: labelExpr,
EvalContext: lCtx,
})
return nil, diags
}
labels = append(labels, labelVal.AsString())
labelRanges = append(labelRanges, labelExpr.Range())
}
block := &hcl.Block{
Type: s.blockType,
TypeRange: s.blockTypeRange,
Labels: labels,
LabelRanges: labelRanges,
DefRange: s.defRange,
Body: s.contentBody,
}
return block, diags
}

View File

@@ -0,0 +1,42 @@
package dynblock
import (
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)
type exprWrap struct {
hcl.Expression
i *iteration
}
func (e exprWrap) Variables() []hcl.Traversal {
raw := e.Expression.Variables()
ret := make([]hcl.Traversal, 0, len(raw))
// Filter out traversals that refer to our iterator name or any
// iterator we've inherited; we're going to provide those in
// our Value wrapper, so the caller doesn't need to know about them.
for _, traversal := range raw {
rootName := traversal.RootName()
if rootName == e.i.IteratorName {
continue
}
if _, inherited := e.i.Inherited[rootName]; inherited {
continue
}
ret = append(ret, traversal)
}
return ret
}
func (e exprWrap) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
extCtx := e.i.EvalContext(ctx)
return e.Expression.Value(extCtx)
}
// UnwrapExpression returns the expression being wrapped by this instance.
// This allows the original expression to be recovered by hcl.UnwrapExpression.
func (e exprWrap) UnwrapExpression() hcl.Expression {
return e.Expression
}

View File

@@ -0,0 +1,66 @@
package dynblock
import (
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)
type iteration struct {
IteratorName string
Key cty.Value
Value cty.Value
Inherited map[string]*iteration
}
func (s *expandSpec) MakeIteration(key, value cty.Value) *iteration {
return &iteration{
IteratorName: s.iteratorName,
Key: key,
Value: value,
Inherited: s.inherited,
}
}
func (i *iteration) Object() cty.Value {
return cty.ObjectVal(map[string]cty.Value{
"key": i.Key,
"value": i.Value,
})
}
func (i *iteration) EvalContext(base *hcl.EvalContext) *hcl.EvalContext {
new := base.NewChild()
if i != nil {
new.Variables = map[string]cty.Value{}
for name, otherIt := range i.Inherited {
new.Variables[name] = otherIt.Object()
}
new.Variables[i.IteratorName] = i.Object()
}
return new
}
func (i *iteration) MakeChild(iteratorName string, key, value cty.Value) *iteration {
if i == nil {
// Create entirely new root iteration, then
return &iteration{
IteratorName: iteratorName,
Key: key,
Value: value,
}
}
inherited := map[string]*iteration{}
for name, otherIt := range i.Inherited {
inherited[name] = otherIt
}
inherited[i.IteratorName] = i
return &iteration{
IteratorName: iteratorName,
Key: key,
Value: value,
Inherited: inherited,
}
}

View File

@@ -0,0 +1,47 @@
// Package dynblock provides an extension to HCL that allows dynamic
// declaration of nested blocks in certain contexts via a special block type
// named "dynamic".
package dynblock
import (
"github.com/hashicorp/hcl/v2"
)
// Expand "dynamic" blocks in the given body, returning a new body that
// has those blocks expanded.
//
// The given EvalContext is used when evaluating "for_each" and "labels"
// attributes within dynamic blocks, allowing those expressions access to
// variables and functions beyond the iterator variable created by the
// iteration.
//
// Expand returns no diagnostics because no blocks are actually expanded
// until a call to Content or PartialContent on the returned body, which
// will then expand only the blocks selected by the schema.
//
// "dynamic" blocks are also expanded automatically within nested blocks
// in the given body, including within other dynamic blocks, thus allowing
// multi-dimensional iteration. However, it is not possible to
// dynamically-generate the "dynamic" blocks themselves except through nesting.
//
// parent {
// dynamic "child" {
// for_each = child_objs
// content {
// dynamic "grandchild" {
// for_each = child.value.children
// labels = [grandchild.key]
// content {
// parent_key = child.key
// value = grandchild.value
// }
// }
// }
// }
// }
func Expand(body hcl.Body, ctx *hcl.EvalContext) hcl.Body {
return &expandBody{
original: body,
forEachCtx: ctx,
}
}

View File

@@ -0,0 +1,50 @@
package dynblock
import "github.com/hashicorp/hcl/v2"
var dynamicBlockHeaderSchema = hcl.BlockHeaderSchema{
Type: "dynamic",
LabelNames: []string{"type"},
}
var dynamicBlockBodySchemaLabels = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "for_each",
Required: true,
},
{
Name: "iterator",
Required: false,
},
{
Name: "labels",
Required: true,
},
},
Blocks: []hcl.BlockHeaderSchema{
{
Type: "content",
LabelNames: nil,
},
},
}
var dynamicBlockBodySchemaNoLabels = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "for_each",
Required: true,
},
{
Name: "iterator",
Required: false,
},
},
Blocks: []hcl.BlockHeaderSchema{
{
Type: "content",
LabelNames: nil,
},
},
}

View File

@@ -0,0 +1,84 @@
package dynblock
import (
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)
// unknownBody is a funny body that just reports everything inside it as
// unknown. It uses a given other body as a sort of template for what attributes
// and blocks are inside -- including source location information -- but
// subsitutes unknown values of unknown type for all attributes.
//
// This rather odd process is used to handle expansion of dynamic blocks whose
// for_each expression is unknown. Since a block cannot itself be unknown,
// we instead arrange for everything _inside_ the block to be unknown instead,
// to give the best possible approximation.
type unknownBody struct {
template hcl.Body
}
var _ hcl.Body = unknownBody{}
func (b unknownBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
content, diags := b.template.Content(schema)
content = b.fixupContent(content)
// We're intentionally preserving the diagnostics reported from the
// inner body so that we can still report where the template body doesn't
// match the requested schema.
return content, diags
}
func (b unknownBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
content, remain, diags := b.template.PartialContent(schema)
content = b.fixupContent(content)
remain = unknownBody{remain} // remaining content must also be wrapped
// We're intentionally preserving the diagnostics reported from the
// inner body so that we can still report where the template body doesn't
// match the requested schema.
return content, remain, diags
}
func (b unknownBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
attrs, diags := b.template.JustAttributes()
attrs = b.fixupAttrs(attrs)
// We're intentionally preserving the diagnostics reported from the
// inner body so that we can still report where the template body doesn't
// match the requested schema.
return attrs, diags
}
func (b unknownBody) MissingItemRange() hcl.Range {
return b.template.MissingItemRange()
}
func (b unknownBody) fixupContent(got *hcl.BodyContent) *hcl.BodyContent {
ret := &hcl.BodyContent{}
ret.Attributes = b.fixupAttrs(got.Attributes)
if len(got.Blocks) > 0 {
ret.Blocks = make(hcl.Blocks, 0, len(got.Blocks))
for _, gotBlock := range got.Blocks {
new := *gotBlock // shallow copy
new.Body = unknownBody{gotBlock.Body} // nested content must also be marked unknown
ret.Blocks = append(ret.Blocks, &new)
}
}
return ret
}
func (b unknownBody) fixupAttrs(got hcl.Attributes) hcl.Attributes {
if len(got) == 0 {
return nil
}
ret := make(hcl.Attributes, len(got))
for name, gotAttr := range got {
new := *gotAttr // shallow copy
new.Expr = hcl.StaticExpr(cty.DynamicVal, gotAttr.Expr.Range())
ret[name] = &new
}
return ret
}

View File

@@ -0,0 +1,209 @@
package dynblock
import (
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)
// WalkVariables begins the recursive process of walking all expressions and
// nested blocks in the given body and its child bodies while taking into
// account any "dynamic" blocks.
//
// This function requires that the caller walk through the nested block
// structure in the given body level-by-level so that an appropriate schema
// can be provided at each level to inform further processing. This workflow
// is thus easiest to use for calling applications that have some higher-level
// schema representation available with which to drive this multi-step
// process. If your application uses the hcldec package, you may be able to
// use VariablesHCLDec instead for a more automatic approach.
func WalkVariables(body hcl.Body) WalkVariablesNode {
return WalkVariablesNode{
body: body,
includeContent: true,
}
}
// WalkExpandVariables is like Variables but it includes only the variables
// required for successful block expansion, ignoring any variables referenced
// inside block contents. The result is the minimal set of all variables
// required for a call to Expand, excluding variables that would only be
// needed to subsequently call Content or PartialContent on the expanded
// body.
func WalkExpandVariables(body hcl.Body) WalkVariablesNode {
return WalkVariablesNode{
body: body,
}
}
type WalkVariablesNode struct {
body hcl.Body
it *iteration
includeContent bool
}
type WalkVariablesChild struct {
BlockTypeName string
Node WalkVariablesNode
}
// Body returns the HCL Body associated with the child node, in case the caller
// wants to do some sort of inspection of it in order to decide what schema
// to pass to Visit.
//
// Most implementations should just fetch a fixed schema based on the
// BlockTypeName field and not access this. Deciding on a schema dynamically
// based on the body is a strange thing to do and generally necessary only if
// your caller is already doing other bizarre things with HCL bodies.
func (c WalkVariablesChild) Body() hcl.Body {
return c.Node.body
}
// Visit returns the variable traversals required for any "dynamic" blocks
// directly in the body associated with this node, and also returns any child
// nodes that must be visited in order to continue the walk.
//
// Each child node has its associated block type name given in its BlockTypeName
// field, which the calling application should use to determine the appropriate
// schema for the content of each child node and pass it to the child node's
// own Visit method to continue the walk recursively.
func (n WalkVariablesNode) Visit(schema *hcl.BodySchema) (vars []hcl.Traversal, children []WalkVariablesChild) {
extSchema := n.extendSchema(schema)
container, _, _ := n.body.PartialContent(extSchema)
if container == nil {
return vars, children
}
children = make([]WalkVariablesChild, 0, len(container.Blocks))
if n.includeContent {
for _, attr := range container.Attributes {
for _, traversal := range attr.Expr.Variables() {
var ours, inherited bool
if n.it != nil {
ours = traversal.RootName() == n.it.IteratorName
_, inherited = n.it.Inherited[traversal.RootName()]
}
if !(ours || inherited) {
vars = append(vars, traversal)
}
}
}
}
for _, block := range container.Blocks {
switch block.Type {
case "dynamic":
blockTypeName := block.Labels[0]
inner, _, _ := block.Body.PartialContent(variableDetectionInnerSchema)
if inner == nil {
continue
}
iteratorName := blockTypeName
if attr, exists := inner.Attributes["iterator"]; exists {
iterTraversal, _ := hcl.AbsTraversalForExpr(attr.Expr)
if len(iterTraversal) == 0 {
// Ignore this invalid dynamic block, since it'll produce
// an error if someone tries to extract content from it
// later anyway.
continue
}
iteratorName = iterTraversal.RootName()
}
blockIt := n.it.MakeChild(iteratorName, cty.DynamicVal, cty.DynamicVal)
if attr, exists := inner.Attributes["for_each"]; exists {
// Filter out iterator names inherited from parent blocks
for _, traversal := range attr.Expr.Variables() {
if _, inherited := blockIt.Inherited[traversal.RootName()]; !inherited {
vars = append(vars, traversal)
}
}
}
if attr, exists := inner.Attributes["labels"]; exists {
// Filter out both our own iterator name _and_ those inherited
// from parent blocks, since we provide _both_ of these to the
// label expressions.
for _, traversal := range attr.Expr.Variables() {
ours := traversal.RootName() == iteratorName
_, inherited := blockIt.Inherited[traversal.RootName()]
if !(ours || inherited) {
vars = append(vars, traversal)
}
}
}
for _, contentBlock := range inner.Blocks {
// We only request "content" blocks in our schema, so we know
// any blocks we find here will be content blocks. We require
// exactly one content block for actual expansion, but we'll
// be more liberal here so that callers can still collect
// variables from erroneous "dynamic" blocks.
children = append(children, WalkVariablesChild{
BlockTypeName: blockTypeName,
Node: WalkVariablesNode{
body: contentBlock.Body,
it: blockIt,
includeContent: n.includeContent,
},
})
}
default:
children = append(children, WalkVariablesChild{
BlockTypeName: block.Type,
Node: WalkVariablesNode{
body: block.Body,
it: n.it,
includeContent: n.includeContent,
},
})
}
}
return vars, children
}
func (n WalkVariablesNode) extendSchema(schema *hcl.BodySchema) *hcl.BodySchema {
// We augment the requested schema to also include our special "dynamic"
// block type, since then we'll get instances of it interleaved with
// all of the literal child blocks we must also include.
extSchema := &hcl.BodySchema{
Attributes: schema.Attributes,
Blocks: make([]hcl.BlockHeaderSchema, len(schema.Blocks), len(schema.Blocks)+1),
}
copy(extSchema.Blocks, schema.Blocks)
extSchema.Blocks = append(extSchema.Blocks, dynamicBlockHeaderSchema)
return extSchema
}
// This is a more relaxed schema than what's in schema.go, since we
// want to maximize the amount of variables we can find even if there
// are erroneous blocks.
var variableDetectionInnerSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "for_each",
Required: false,
},
{
Name: "labels",
Required: false,
},
{
Name: "iterator",
Required: false,
},
},
Blocks: []hcl.BlockHeaderSchema{
{
Type: "content",
},
},
}

View File

@@ -0,0 +1,43 @@
package dynblock
import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcldec"
)
// VariablesHCLDec is a wrapper around WalkVariables that uses the given hcldec
// specification to automatically drive the recursive walk through nested
// blocks in the given body.
//
// This is a drop-in replacement for hcldec.Variables which is able to treat
// blocks of type "dynamic" in the same special way that dynblock.Expand would,
// exposing both the variables referenced in the "for_each" and "labels"
// arguments and variables used in the nested "content" block.
func VariablesHCLDec(body hcl.Body, spec hcldec.Spec) []hcl.Traversal {
rootNode := WalkVariables(body)
return walkVariablesWithHCLDec(rootNode, spec)
}
// ExpandVariablesHCLDec is like VariablesHCLDec but it includes only the
// minimal set of variables required to call Expand, ignoring variables that
// are referenced only inside normal block contents. See WalkExpandVariables
// for more information.
func ExpandVariablesHCLDec(body hcl.Body, spec hcldec.Spec) []hcl.Traversal {
rootNode := WalkExpandVariables(body)
return walkVariablesWithHCLDec(rootNode, spec)
}
func walkVariablesWithHCLDec(node WalkVariablesNode, spec hcldec.Spec) []hcl.Traversal {
vars, children := node.Visit(hcldec.ImpliedSchema(spec))
if len(children) > 0 {
childSpecs := hcldec.ChildBlockTypes(spec)
for _, child := range children {
if childSpec, exists := childSpecs[child.BlockTypeName]; exists {
vars = append(vars, walkVariablesWithHCLDec(child.Node, childSpec)...)
}
}
}
return vars
}

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

Some files were not shown because too many files have changed in this diff Show More