Merge pull request #356 from cdrage/update-vendoring

Update vendoring as well as libcompose
This commit is contained in:
Charlie Drage 2017-01-12 12:49:14 -05:00 committed by GitHub
commit bf6085dd02
28 changed files with 649 additions and 433 deletions

20
glide.lock generated
View File

@ -1,5 +1,5 @@
hash: c7cb14f4249738a47020f9dc1964832921c3f5b8bf5a1c50f5b2fa15eaebb6fe
updated: 2016-12-26T10:22:49.439519344+05:30
hash: 88aef2926941901e180522117085c9a72141a988d1284778fc867f704e43dab0
updated: 2017-01-03T14:38:14.561105912-05:00
imports:
- name: cloud.google.com/go
version: 3b1ae45394a234c385be014e9a488f2bb6eef821
@ -190,7 +190,7 @@ imports:
- name: github.com/docker/go-units
version: 0bbddae09c5a5419a8c6dcdd7ff90da3d450393b
- name: github.com/docker/libcompose
version: fbdac0a6a80837c63eb6c8f43514f7bb3f32df6c
version: 2e320f69cafc70683514ee6226d40e2fe868e6d3
subpackages:
- config
- logger
@ -210,7 +210,7 @@ imports:
- name: github.com/evanphx/json-patch
version: 465937c80b3c07a7c7ad20cc934898646a91c1de
- name: github.com/fatih/structs
version: dc3312cb1a4513a366c4c9e622ad55c32df12ed3
version: a720dfa8df582c51dee1b36feabb906bde1588bd
- name: github.com/flynn/go-shlex
version: 3f9db97f856818214da2e1057f8ad84803971cff
- name: github.com/fsnotify/fsnotify
@ -405,7 +405,7 @@ imports:
- name: github.com/pelletier/go-buffruneio
version: df1e16fde7fc330a0ca68167c23bf7ed6ac31d6d
- name: github.com/pelletier/go-toml
version: 017119f7a78a0b5fc0ea39ef6be09f03acf3345d
version: 439fbba1f887c286024370cb4f281ba815c4c7d7
- name: github.com/prometheus/client_golang
version: e51041b3fa41cece0dca035740ba6411905be473
subpackages:
@ -425,15 +425,15 @@ imports:
- name: github.com/Sirupsen/logrus
version: 881bee4e20a5d11a6a88a5667c6f292072ac1963
- name: github.com/spf13/afero
version: 2f30b2a92c0e5700bcfe4715891adb1f2a7a406d
version: 90dd71edc4d0a8b3511dc12ea15d617d03be09e0
subpackages:
- mem
- name: github.com/spf13/cast
version: 24b6558033ffe202bf42f0f3b870dcc798dd2ba8
version: 56a7ecbeb18dde53c6db4bd96b541fd9741b8d44
- name: github.com/spf13/cobra
version: b62566898a99f2db9c68ed0026aa0a052e59678d
version: 1dd5ff2e11b6dca62fdcb275eb804b94607d8b06
- name: github.com/spf13/jwalterweatherman
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
version: c3ba57c1a2668b9484e43bf9932c88f71992f977
- name: github.com/spf13/pflag
version: 25f8b5b07aece3207895bf19f7ab517eb3b22a40
- name: github.com/spf13/viper
@ -447,7 +447,7 @@ imports:
- name: github.com/xeipuuv/gojsonreference
version: e02fc20de94c78484cd5ffb007f8af96be030a45
- name: github.com/xeipuuv/gojsonschema
version: 59f99ebfe5f712a0055e321231fc3be94e4e00b2
version: f06f290571ce81ab347174c6f7ad2e1865af41a7
- name: golang.org/x/net
version: e90d6d0afc4c315a0d87a568ae68577cc15149a0
subpackages:

View File

@ -11,7 +11,7 @@ import:
- package: github.com/spf13/viper
- package: github.com/docker/libcompose
version: fbdac0a6a80837c63eb6c8f43514f7bb3f32df6c
version: 2e320f69cafc70683514ee6226d40e2fe868e6d3
subpackages:
- config
- lookup

View File

@ -82,11 +82,16 @@ func checkUnsupportedKey(composeProject *project.Project) []string {
// collect all keys found in project
var keysFound []string
// Root level keys
// volume config and network config are not supported
if len(composeProject.NetworkConfigs) > 0 {
// Root level keys are not yet supported
// Check to see if the default network is available and length is only equal to one.
// Else, warn the user that root level networks are not supported (yet)
if _, ok := composeProject.NetworkConfigs["default"]; ok && len(composeProject.NetworkConfigs) == 1 {
logrus.Debug("Default network found")
} else if len(composeProject.NetworkConfigs) > 0 {
keysFound = append(keysFound, "root level networks")
}
// Root level volumes are not yet supported
if len(composeProject.VolumeConfigs) > 0 {
keysFound = append(keysFound, "root level volumes")
}

View File

@ -22,7 +22,7 @@ source $KOMPOSE_ROOT/script/test/cmd/lib.sh
convert::expect_failure "kompose -f $KOMPOSE_ROOT/script/test/fixtures/etherpad/docker-compose.yml convert --stdout"
# commenting this test case out until image handling is fixed
#convert::expect_failure "kompose -f $KOMPOSE_ROOT/script/test/fixtures/etherpad/docker-compose-no-image.yml convert --stdout"
convert::expect_failure "kompose -f $KOMPOSE_ROOT/script/test/fixtures/etherpad/docker-compose-no-image.yml convert --stdout"
convert::expect_warning "kompose -f $KOMPOSE_ROOT/script/test/fixtures/etherpad/docker-compose-no-ports.yml convert --stdout" "Service cannot be created because of missing port."
export $(cat $KOMPOSE_ROOT/script/test/fixtures/etherpad/envs)
# kubernetes test

View File

@ -102,7 +102,7 @@ func parseLine(line string, mapping func(string) string) (string, bool) {
return buffer.String(), true
}
func parseConfig(option, service string, data *interface{}, mapping func(string) string) error {
func parseConfig(key string, data *interface{}, mapping func(string) string) error {
switch typedData := (*data).(type) {
case string:
var success bool
@ -110,11 +110,11 @@ func parseConfig(option, service string, data *interface{}, mapping func(string)
*data, success = parseLine(typedData, mapping)
if !success {
return fmt.Errorf("Invalid interpolation format for \"%s\" option in service \"%s\": \"%s\"", option, service, typedData)
return fmt.Errorf("Invalid interpolation format for key \"%s\": \"%s\"", key, typedData)
}
case []interface{}:
for k, v := range typedData {
err := parseConfig(option, service, &v, mapping)
err := parseConfig(key, &v, mapping)
if err != nil {
return err
@ -124,7 +124,7 @@ func parseConfig(option, service string, data *interface{}, mapping func(string)
}
case map[interface{}]interface{}:
for k, v := range typedData {
err := parseConfig(option, service, &v, mapping)
err := parseConfig(key, &v, mapping)
if err != nil {
return err
@ -137,33 +137,21 @@ func parseConfig(option, service string, data *interface{}, mapping func(string)
return nil
}
// Interpolate replaces variables in the raw map representation of the project file
func Interpolate(environmentLookup EnvironmentLookup, config *RawServiceMap) error {
for k, v := range *config {
for k2, v2 := range v {
err := parseConfig(k2, k, &v2, func(s string) string {
values := environmentLookup.Lookup(s, k, nil)
// Interpolate replaces variables in a map entry
func Interpolate(key string, data *interface{}, environmentLookup EnvironmentLookup) error {
return parseConfig(key, data, func(s string) string {
values := environmentLookup.Lookup(s, nil)
if len(values) == 0 {
logrus.Warnf("The %s variable is not set. Substituting a blank string.", s)
return ""
}
// Use first result if many are given
value := values[0]
// Environment variables come in key=value format
// Return everything past first '='
return strings.SplitN(value, "=", 2)[1]
})
if err != nil {
return err
}
(*config)[k][k2] = v2
if len(values) == 0 {
logrus.Warnf("The %s variable is not set. Substituting a blank string.", s)
return ""
}
}
return nil
// Use first result if many are given
value := values[0]
// Environment variables come in key=value format
// Return everything past first '='
return strings.SplitN(value, "=", 2)[1]
})
}

View File

@ -29,18 +29,8 @@ func CreateConfig(bytes []byte) (*Config, error) {
if err := yaml.Unmarshal(bytes, &config); err != nil {
return nil, err
}
if config.Version == "2" {
for key, value := range config.Networks {
if value == nil {
config.Networks[key] = &NetworkConfig{}
}
}
for key, value := range config.Volumes {
if value == nil {
config.Volumes[key] = &VolumeConfig{}
}
}
} else {
if config.Version != "2" {
var baseRawServices RawServiceMap
if err := yaml.Unmarshal(bytes, &baseRawServices); err != nil {
return nil, err
@ -48,6 +38,13 @@ func CreateConfig(bytes []byte) (*Config, error) {
config.Services = baseRawServices
}
if config.Volumes == nil {
config.Volumes = make(map[string]interface{})
}
if config.Networks == nil {
config.Networks = make(map[string]interface{})
}
return &config, nil
}
@ -63,6 +60,34 @@ func Merge(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup
}
baseRawServices := config.Services
if options.Interpolate {
if err := InterpolateRawServiceMap(&baseRawServices, environmentLookup); err != nil {
return "", nil, nil, nil, err
}
for k, v := range config.Volumes {
if err := Interpolate(k, &v, environmentLookup); err != nil {
return "", nil, nil, nil, err
}
config.Volumes[k] = v
}
for k, v := range config.Networks {
if err := Interpolate(k, &v, environmentLookup); err != nil {
return "", nil, nil, nil, err
}
config.Networks[k] = v
}
}
if options.Preprocess != nil {
var err error
baseRawServices, err = options.Preprocess(baseRawServices)
if err != nil {
return "", nil, nil, nil, err
}
}
var serviceConfigs map[string]*ServiceConfig
if config.Version == "2" {
var err error
@ -91,7 +116,29 @@ func Merge(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup
}
}
return config.Version, serviceConfigs, config.Volumes, config.Networks, nil
var volumes map[string]*VolumeConfig
var networks map[string]*NetworkConfig
if err := utils.Convert(config.Volumes, &volumes); err != nil {
return "", nil, nil, nil, err
}
if err := utils.Convert(config.Networks, &networks); err != nil {
return "", nil, nil, nil, err
}
return config.Version, serviceConfigs, volumes, networks, nil
}
// InterpolateRawServiceMap replaces varialbse in raw service map struct based on environment lookup
func InterpolateRawServiceMap(baseRawServices *RawServiceMap, environmentLookup EnvironmentLookup) error {
for k, v := range *baseRawServices {
for k2, v2 := range v {
if err := Interpolate(k2, &v2, environmentLookup); err != nil {
return err
}
(*baseRawServices)[k][k2] = v2
}
}
return nil
}
func adjustValues(configs map[string]*ServiceConfig) {

View File

@ -10,20 +10,6 @@ import (
// MergeServicesV1 merges a v1 compose file into an existing set of service configs
func MergeServicesV1(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, datas RawServiceMap, options *ParseOptions) (map[string]*ServiceConfigV1, error) {
if options.Interpolate {
if err := Interpolate(environmentLookup, &datas); err != nil {
return nil, err
}
}
if options.Preprocess != nil {
var err error
datas, err = options.Preprocess(datas)
if err != nil {
return nil, err
}
}
if options.Validate {
if err := validate(datas); err != nil {
return nil, err
@ -117,8 +103,7 @@ func parseV1(resourceLookup ResourceLookup, environmentLookup EnvironmentLookup,
baseRawServices := config.Services
if options.Interpolate {
err = Interpolate(environmentLookup, &baseRawServices)
if err != nil {
if err = InterpolateRawServiceMap(&baseRawServices, environmentLookup); err != nil {
return nil, err
}
}

View File

@ -3,6 +3,7 @@ package config
import (
"fmt"
"path"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/libcompose/utils"
@ -10,20 +11,6 @@ import (
// MergeServicesV2 merges a v2 compose file into an existing set of service configs
func MergeServicesV2(existingServices *ServiceConfigs, environmentLookup EnvironmentLookup, resourceLookup ResourceLookup, file string, datas RawServiceMap, options *ParseOptions) (map[string]*ServiceConfig, error) {
if options.Interpolate {
if err := Interpolate(environmentLookup, &datas); err != nil {
return nil, err
}
}
if options.Preprocess != nil {
var err error
datas, err = options.Preprocess(datas)
if err != nil {
return nil, err
}
}
if options.Validate {
if err := validateV2(datas); err != nil {
return nil, err
@ -49,6 +36,19 @@ func MergeServicesV2(existingServices *ServiceConfigs, environmentLookup Environ
datas[name] = data
}
if options.Validate {
var errs []string
for name, data := range datas {
err := validateServiceConstraintsv2(data, name)
if err != nil {
errs = append(errs, err.Error())
}
}
if len(errs) != 0 {
return nil, fmt.Errorf(strings.Join(errs, "\n"))
}
}
serviceConfigs := make(map[string]*ServiceConfig)
if err := utils.Convert(datas, &serviceConfigs); err != nil {
return nil, err
@ -108,8 +108,7 @@ func parseV2(resourceLookup ResourceLookup, environmentLookup EnvironmentLookup,
baseRawServices := config.Services
if options.Interpolate {
err = Interpolate(environmentLookup, &baseRawServices)
if err != nil {
if err = InterpolateRawServiceMap(&baseRawServices, environmentLookup); err != nil {
return nil, err
}
}

View File

@ -8,7 +8,7 @@ import (
// EnvironmentLookup defines methods to provides environment variable loading.
type EnvironmentLookup interface {
Lookup(key, serviceName string, config *ServiceConfig) []string
Lookup(key string, config *ServiceConfig) []string
}
// ResourceLookup defines methods to provides file loading.
@ -30,22 +30,27 @@ type ServiceConfigV1 struct {
ContainerName string `yaml:"container_name,omitempty"`
Devices []string `yaml:"devices,omitempty"`
DNS yaml.Stringorslice `yaml:"dns,omitempty"`
DNSOpts []string `yaml:"dns_opt,omitempty"`
DNSSearch yaml.Stringorslice `yaml:"dns_search,omitempty"`
Dockerfile string `yaml:"dockerfile,omitempty"`
DomainName string `yaml:"domainname,omitempty"`
Entrypoint yaml.Command `yaml:"entrypoint,flow,omitempty"`
EnvFile yaml.Stringorslice `yaml:"env_file,omitempty"`
Environment yaml.MaporEqualSlice `yaml:"environment,omitempty"`
GroupAdd []string `yaml:"group_add,omitempty"`
Hostname string `yaml:"hostname,omitempty"`
Image string `yaml:"image,omitempty"`
Isolation string `yaml:"isolation,omitempty"`
Labels yaml.SliceorMap `yaml:"labels,omitempty"`
Links yaml.MaporColonSlice `yaml:"links,omitempty"`
LogDriver string `yaml:"log_driver,omitempty"`
MacAddress string `yaml:"mac_address,omitempty"`
MemLimit yaml.StringorInt `yaml:"mem_limit,omitempty"`
MemSwapLimit yaml.StringorInt `yaml:"memswap_limit,omitempty"`
MemLimit yaml.MemStringorInt `yaml:"mem_limit,omitempty"`
MemSwapLimit yaml.MemStringorInt `yaml:"memswap_limit,omitempty"`
MemSwappiness yaml.MemStringorInt `yaml:"mem_swappiness,omitempty"`
Name string `yaml:"name,omitempty"`
Net string `yaml:"net,omitempty"`
OomScoreAdj yaml.StringorInt `yaml:"oom_score_adj,omitempty"`
Pid string `yaml:"pid,omitempty"`
Uts string `yaml:"uts,omitempty"`
Ipc string `yaml:"ipc,omitempty"`
@ -53,9 +58,11 @@ type ServiceConfigV1 struct {
Privileged bool `yaml:"privileged,omitempty"`
Restart string `yaml:"restart,omitempty"`
ReadOnly bool `yaml:"read_only,omitempty"`
ShmSize yaml.StringorInt `yaml:"shm_size,omitempty"`
ShmSize yaml.MemStringorInt `yaml:"shm_size,omitempty"`
StdinOpen bool `yaml:"stdin_open,omitempty"`
SecurityOpt []string `yaml:"security_opt,omitempty"`
StopSignal string `yaml:"stop_signal,omitempty"`
Tmpfs yaml.Stringorslice `yaml:"tmpfs,omitempty"`
Tty bool `yaml:"tty,omitempty"`
User string `yaml:"user,omitempty"`
VolumeDriver string `yaml:"volume_driver,omitempty"`
@ -89,6 +96,7 @@ type ServiceConfig struct {
Devices []string `yaml:"devices,omitempty"`
DependsOn []string `yaml:"depends_on,omitempty"`
DNS yaml.Stringorslice `yaml:"dns,omitempty"`
DNSOpts []string `yaml:"dns_opt,omitempty"`
DNSSearch yaml.Stringorslice `yaml:"dns_search,omitempty"`
DomainName string `yaml:"domainname,omitempty"`
Entrypoint yaml.Command `yaml:"entrypoint,flow,omitempty"`
@ -98,23 +106,28 @@ type ServiceConfig struct {
Extends yaml.MaporEqualSlice `yaml:"extends,omitempty"`
ExternalLinks []string `yaml:"external_links,omitempty"`
ExtraHosts []string `yaml:"extra_hosts,omitempty"`
GroupAdd []string `yaml:"group_add,omitempty"`
Image string `yaml:"image,omitempty"`
Isolation string `yaml:"isolation,omitempty"`
Hostname string `yaml:"hostname,omitempty"`
Ipc string `yaml:"ipc,omitempty"`
Labels yaml.SliceorMap `yaml:"labels,omitempty"`
Links yaml.MaporColonSlice `yaml:"links,omitempty"`
Logging Log `yaml:"logging,omitempty"`
MacAddress string `yaml:"mac_address,omitempty"`
MemLimit yaml.StringorInt `yaml:"mem_limit,omitempty"`
MemSwapLimit yaml.StringorInt `yaml:"memswap_limit,omitempty"`
MemLimit yaml.MemStringorInt `yaml:"mem_limit,omitempty"`
MemSwapLimit yaml.MemStringorInt `yaml:"memswap_limit,omitempty"`
MemSwappiness yaml.MemStringorInt `yaml:"mem_swappiness,omitempty"`
NetworkMode string `yaml:"network_mode,omitempty"`
Networks *yaml.Networks `yaml:"networks,omitempty"`
OomScoreAdj yaml.StringorInt `yaml:"oom_score_adj,omitempty"`
Pid string `yaml:"pid,omitempty"`
Ports []string `yaml:"ports,omitempty"`
Privileged bool `yaml:"privileged,omitempty"`
SecurityOpt []string `yaml:"security_opt,omitempty"`
ShmSize yaml.StringorInt `yaml:"shm_size,omitempty"`
ShmSize yaml.MemStringorInt `yaml:"shm_size,omitempty"`
StopSignal string `yaml:"stop_signal,omitempty"`
Tmpfs yaml.Stringorslice `yaml:"tmpfs,omitempty"`
VolumeDriver string `yaml:"volume_driver,omitempty"`
Volumes *yaml.Volumes `yaml:"volumes,omitempty"`
VolumesFrom []string `yaml:"volumes_from,omitempty"`
@ -159,10 +172,10 @@ type NetworkConfig struct {
// Config holds libcompose top level configuration
type Config struct {
Version string `yaml:"version,omitempty"`
Services RawServiceMap `yaml:"services,omitempty"`
Volumes map[string]*VolumeConfig `yaml:"volumes,omitempty"`
Networks map[string]*NetworkConfig `yaml:"networks,omitempty"`
Version string `yaml:"version,omitempty"`
Services RawServiceMap `yaml:"services,omitempty"`
Volumes map[string]interface{} `yaml:"volumes,omitempty"`
Networks map[string]interface{} `yaml:"networks,omitempty"`
}
// NewServiceConfigs initializes a new Configs struct

View File

@ -5,6 +5,7 @@ import (
"strconv"
"strings"
"github.com/docker/libcompose/utils"
"github.com/xeipuuv/gojsonschema"
)
@ -70,55 +71,22 @@ func getValue(val interface{}, context string) string {
return ""
}
// Converts map[interface{}]interface{} to map[string]interface{} recursively
// gojsonschema only accepts map[string]interface{}
func convertServiceMapKeysToStrings(serviceMap RawServiceMap) RawServiceMap {
newServiceMap := make(RawServiceMap)
for k, v := range serviceMap {
newServiceMap[k] = convertServiceKeysToStrings(v)
}
return newServiceMap
}
func convertServiceKeysToStrings(service RawService) RawService {
newService := make(RawService)
for k, v := range service {
newService[k] = convertKeysToStrings(v)
newService[k] = utils.ConvertKeysToStrings(v)
}
return newService
}
func convertKeysToStrings(item interface{}) interface{} {
switch typedDatas := item.(type) {
case map[interface{}]interface{}:
newMap := make(map[string]interface{})
for key, value := range typedDatas {
stringKey := key.(string)
newMap[stringKey] = convertKeysToStrings(value)
}
return newMap
case []interface{}:
// newArray := make([]interface{}, 0) will cause golint to complain
var newArray []interface{}
newArray = make([]interface{}, 0)
for _, value := range typedDatas {
newArray = append(newArray, convertKeysToStrings(value))
}
return newArray
default:
return item
}
}
var dockerConfigHints = map[string]string{
"cpu_share": "cpu_shares",
"add_host": "extra_hosts",
@ -319,3 +287,36 @@ func validateServiceConstraints(service RawService, serviceName string) error {
return nil
}
func validateServiceConstraintsv2(service RawService, serviceName string) error {
if err := setupSchemaLoaders(servicesSchemaDataV2, &schemaV2, &schemaLoaderV2, &constraintSchemaLoaderV2); err != nil {
return err
}
service = convertServiceKeysToStrings(service)
var validationErrors []string
dataLoader := gojsonschema.NewGoLoader(service)
result, err := gojsonschema.Validate(constraintSchemaLoaderV2, dataLoader)
if err != nil {
return err
}
if !result.Valid() {
for _, err := range result.Errors() {
if err.Type() == "required" {
_, containsImage := service["image"]
_, containsBuild := service["build"]
if containsBuild || !containsImage && !containsBuild {
validationErrors = append(validationErrors, fmt.Sprintf("Service '%s' has neither an image nor a build context specified. At least one must be provided.", serviceName))
}
}
}
return fmt.Errorf(strings.Join(validationErrors, "\n"))
}
return nil
}

View File

@ -13,10 +13,10 @@ type ComposableEnvLookup struct {
// Lookup creates a string slice of string containing a "docker-friendly" environment string
// in the form of 'key=value'. It loop through the lookups and returns the latest value if
// more than one lookup return a result.
func (l *ComposableEnvLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string {
func (l *ComposableEnvLookup) Lookup(key string, config *config.ServiceConfig) []string {
result := []string{}
for _, lookup := range l.Lookups {
env := lookup.Lookup(key, serviceName, config)
env := lookup.Lookup(key, config)
if len(env) == 1 {
result = env
}

View File

@ -16,7 +16,7 @@ type EnvfileLookup struct {
// Lookup creates a string slice of string containing a "docker-friendly" environment string
// in the form of 'key=value'. It gets environment values using a '.env' file in the specified
// path.
func (l *EnvfileLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string {
func (l *EnvfileLookup) Lookup(key string, config *config.ServiceConfig) []string {
envs, err := opts.ParseEnvFile(l.Path)
if err != nil {
return []string{}

View File

@ -15,7 +15,7 @@ type OsEnvLookup struct {
// in the form of 'key=value'. It gets environment values using os.Getenv.
// If the os environment variable does not exists, the slice is empty. serviceName and config
// are not used at all in this implementation.
func (o *OsEnvLookup) Lookup(key, serviceName string, config *config.ServiceConfig) []string {
func (o *OsEnvLookup) Lookup(key string, config *config.ServiceConfig) []string {
ret := os.Getenv(key)
if ret == "" {
return []string{}

View File

@ -141,7 +141,7 @@ func (p *Project) CreateService(name string) (Service, error) {
env = parts[0]
}
for _, value := range p.context.EnvironmentLookup.Lookup(env, name, &config) {
for _, value := range p.context.EnvironmentLookup.Lookup(env, &config) {
parsedEnv = append(parsedEnv, value)
}
}
@ -151,7 +151,7 @@ func (p *Project) CreateService(name string) (Service, error) {
// check the environment for extra build Args that are set but not given a value in the compose file
for arg, value := range config.Build.Args {
if value == "\x00" {
envValue := p.context.EnvironmentLookup.Lookup(arg, name, &config)
envValue := p.context.EnvironmentLookup.Lookup(arg, &config)
// depending on what we get back we do different things
switch l := len(envValue); l {
case 0:
@ -160,7 +160,7 @@ func (p *Project) CreateService(name string) (Service, error) {
parts := strings.SplitN(envValue[0], "=", 2)
config.Build.Args[parts[0]] = parts[1]
default:
return nil, fmt.Errorf("Tried to set Build Arg %#v to multi-value %#v.", arg, envValue)
return nil, fmt.Errorf("tried to set Build Arg %#v to multi-value %#v", arg, envValue)
}
}
}
@ -267,10 +267,12 @@ func (p *Project) handleNetworkConfig() {
serviceConfig.Networks = &yaml.Networks{
Networks: []*yaml.Network{
{
Name: "default",
Name: "default",
RealName: fmt.Sprintf("%s_%s", p.Name, "default"),
},
},
}
p.AddNetworkConfig("default", &config.NetworkConfig{})
}
// Consolidate the name of the network
// FIXME(vdemeester) probably shouldn't be there, maybe move that to interface/factory

View File

@ -14,6 +14,9 @@ func (p *Project) Create(ctx context.Context, options options.Create, services .
if options.NoRecreate && options.ForceRecreate {
return fmt.Errorf("no-recreate and force-recreate cannot be combined")
}
if err := p.initialize(ctx); err != nil {
return err
}
return p.perform(events.ProjectCreateStart, events.ProjectCreateDone, services, wrapperAction(func(wrapper *serviceWrapper, wrappers map[string]*serviceWrapper) {
wrapper.Do(wrappers, events.ServiceCreateStart, events.ServiceCreate, func(service Service) error {
return service.Create(ctx, options)

View File

@ -1,13 +1,17 @@
package project
import (
"golang.org/x/net/context"
)
import "golang.org/x/net/context"
// Ps list containers for the specified services.
func (p *Project) Ps(ctx context.Context, services ...string) (InfoSet, error) {
allInfo := InfoSet{}
for _, name := range p.ServiceConfigs.Keys() {
if len(services) == 0 {
services = p.ServiceConfigs.Keys()
}
for _, name := range services {
service, err := p.CreateService(name)
if err != nil {
return nil, err

View File

@ -135,3 +135,28 @@ func Merge(coll1, coll2 []string) []string {
}
return r
}
// ConvertKeysToStrings converts map[interface{}] to map[string] recursively
func ConvertKeysToStrings(item interface{}) interface{} {
switch typedDatas := item.(type) {
case map[string]interface{}:
for key, value := range typedDatas {
typedDatas[key] = ConvertKeysToStrings(value)
}
return typedDatas
case map[interface{}]interface{}:
newMap := make(map[string]interface{})
for key, value := range typedDatas {
stringKey := key.(string)
newMap[stringKey] = ConvertKeysToStrings(value)
}
return newMap
case []interface{}:
for i, value := range typedDatas {
typedDatas[i] = ConvertKeysToStrings(value)
}
return typedDatas
default:
return item
}
}

View File

@ -7,6 +7,7 @@ import (
"strings"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/go-units"
)
// StringorInt represents a string or an integer.
@ -23,6 +24,7 @@ func (s *StringorInt) UnmarshalYAML(unmarshal func(interface{}) error) error {
var stringType string
if err := unmarshal(&stringType); err == nil {
intType, err := strconv.ParseInt(stringType, 10, 64)
if err != nil {
return err
}
@ -33,6 +35,32 @@ func (s *StringorInt) UnmarshalYAML(unmarshal func(interface{}) error) error {
return errors.New("Failed to unmarshal StringorInt")
}
// MemStringorInt represents a string or an integer
// the String supports notations like 10m for then Megabyte of memory
type MemStringorInt int64
// UnmarshalYAML implements the Unmarshaller interface.
func (s *MemStringorInt) UnmarshalYAML(unmarshal func(interface{}) error) error {
var intType int64
if err := unmarshal(&intType); err == nil {
*s = MemStringorInt(intType)
return nil
}
var stringType string
if err := unmarshal(&stringType); err == nil {
intType, err := units.RAMInBytes(stringType)
if err != nil {
return err
}
*s = MemStringorInt(intType)
return nil
}
return errors.New("Failed to unmarshal MemStringorInt")
}
// Stringorslice represents
// Using engine-api Strslice and augment it with YAML marshalling stuff. a string or an array of strings.
type Stringorslice strslice.StrSlice

View File

@ -530,15 +530,22 @@ func (s *Struct) nested(val reflect.Value) interface{} {
finalVal = m
}
case reflect.Map:
v := val.Type().Elem()
if v.Kind() == reflect.Ptr {
v = v.Elem()
// get the element type of the map
mapElem := val.Type()
switch val.Type().Kind() {
case reflect.Ptr, reflect.Array, reflect.Map,
reflect.Slice, reflect.Chan:
mapElem = val.Type().Elem()
if mapElem.Kind() == reflect.Ptr {
mapElem = mapElem.Elem()
}
}
// only iterate over struct types, ie: map[string]StructType,
// map[string][]StructType,
if v.Kind() == reflect.Struct ||
(v.Kind() == reflect.Slice && v.Elem().Kind() == reflect.Struct) {
if mapElem.Kind() == reflect.Struct ||
(mapElem.Kind() == reflect.Slice &&
mapElem.Elem().Kind() == reflect.Struct) {
m := make(map[string]interface{}, val.Len())
for _, k := range val.MapKeys() {
m[k.String()] = s.nested(val.MapIndex(k))

View File

@ -131,7 +131,7 @@ func (l *tomlLexer) lexVoid() tomlLexStateFn {
case '[':
return l.lexTableKey
case '#':
return l.lexComment
return l.lexComment(l.lexVoid)
case '=':
return l.lexEqual
case '\r':
@ -182,7 +182,7 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
case '}':
return l.lexRightCurlyBrace
case '#':
return l.lexComment
return l.lexComment(l.lexRvalue)
case '"':
return l.lexString
case '\'':
@ -309,15 +309,17 @@ func (l *tomlLexer) lexKey() tomlLexStateFn {
return l.lexVoid
}
func (l *tomlLexer) lexComment() tomlLexStateFn {
for next := l.peek(); next != '\n' && next != eof; next = l.peek() {
if next == '\r' && l.follow("\r\n") {
break
func (l *tomlLexer) lexComment(previousState tomlLexStateFn) tomlLexStateFn {
return func() tomlLexStateFn {
for next := l.peek(); next != '\n' && next != eof; next = l.peek() {
if next == '\r' && l.follow("\r\n") {
break
}
l.next()
}
l.next()
l.ignore()
return previousState
}
l.ignore()
return l.lexVoid
}
func (l *tomlLexer) lexLeftBracket() tomlLexStateFn {

View File

@ -128,14 +128,16 @@ func (m *MemMapFs) Mkdir(name string, perm os.FileMode) error {
m.mu.RUnlock()
if ok {
return &os.PathError{"mkdir", name, ErrFileExists}
} else {
m.mu.Lock()
item := mem.CreateDir(name)
m.getData()[name] = item
m.registerWithParent(item)
m.mu.Unlock()
m.Chmod(name, perm)
}
m.mu.Lock()
item := mem.CreateDir(name)
m.getData()[name] = item
m.registerWithParent(item)
m.mu.Unlock()
m.Chmod(name, perm)
return nil
}
@ -313,7 +315,10 @@ func (m *MemMapFs) Stat(name string) (os.FileInfo, error) {
func (m *MemMapFs) Chmod(name string, mode os.FileMode) error {
name = normalizePath(name)
m.mu.RLock()
f, ok := m.getData()[name]
m.mu.RUnlock()
if !ok {
return &os.PathError{"chmod", name, ErrFileNotFound}
}
@ -327,7 +332,10 @@ func (m *MemMapFs) Chmod(name string, mode os.FileMode) error {
func (m *MemMapFs) Chtimes(name string, atime time.Time, mtime time.Time) error {
name = normalizePath(name)
m.mu.RLock()
f, ok := m.getData()[name]
m.mu.RUnlock()
if !ok {
return &os.PathError{"chtimes", name, ErrFileNotFound}
}

View File

@ -18,15 +18,21 @@ import (
func ToTimeE(i interface{}) (tim time.Time, err error) {
i = indirect(i)
switch s := i.(type) {
switch v := i.(type) {
case time.Time:
return s, nil
return v, nil
case string:
d, e := StringToDate(s)
d, e := StringToDate(v)
if e == nil {
return d, nil
}
return time.Time{}, fmt.Errorf("Could not parse Date/Time format: %v\n", e)
case int:
return time.Unix(int64(v), 0), nil
case int32:
return time.Unix(int64(v), 0), nil
case int64:
return time.Unix(v, 0), nil
default:
return time.Time{}, fmt.Errorf("Unable to Cast %#v to Time\n", i)
}

View File

@ -0,0 +1,103 @@
// Copyright © 2016 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package jwalterweatherman
import (
"io"
"io/ioutil"
"log"
"os"
)
var (
TRACE *log.Logger
DEBUG *log.Logger
INFO *log.Logger
WARN *log.Logger
ERROR *log.Logger
CRITICAL *log.Logger
FATAL *log.Logger
LOG *log.Logger
FEEDBACK *Feedback
defaultNotepad *Notepad
)
func reloadDefaultNotepad() {
TRACE = defaultNotepad.TRACE
DEBUG = defaultNotepad.DEBUG
INFO = defaultNotepad.INFO
WARN = defaultNotepad.WARN
ERROR = defaultNotepad.ERROR
CRITICAL = defaultNotepad.CRITICAL
FATAL = defaultNotepad.FATAL
LOG = defaultNotepad.LOG
FEEDBACK = defaultNotepad.FEEDBACK
}
func init() {
defaultNotepad = NewNotepad(LevelInfo, LevelTrace, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime)
reloadDefaultNotepad()
}
// SetLogThreshold set the log threshold for the default notepad. Trace by default.
func SetLogThreshold(threshold Threshold) {
defaultNotepad.SetLogThreshold(threshold)
reloadDefaultNotepad()
}
// SetLogOutput set the log output for the default notepad. Discarded by default.
func SetLogOutput(handle io.Writer) {
defaultNotepad.SetLogOutput(handle)
reloadDefaultNotepad()
}
// SetStdoutThreshold set the standard output threshold for the default notepad.
// Info by default.
func SetStdoutThreshold(threshold Threshold) {
defaultNotepad.SetStdoutThreshold(threshold)
reloadDefaultNotepad()
}
// SetPrefix set the prefix for the default logger. Empty by default.
func SetPrefix(prefix string) {
defaultNotepad.SetPrefix(prefix)
reloadDefaultNotepad()
}
// SetFlags set the flags for the default logger. "log.Ldate | log.Ltime" by default.
func SetFlags(flags int) {
defaultNotepad.SetFlags(flags)
reloadDefaultNotepad()
}
// Level returns the current global log threshold.
func LogThreshold() Threshold {
return defaultNotepad.logThreshold
}
// Level returns the current global output threshold.
func StdoutThreshold() Threshold {
return defaultNotepad.stdoutThreshold
}
// LogCountForLevel returns the number of log invocations for a given threshold.
func LogCountForLevel(l Threshold) uint64 {
return defaultNotepad.LogCountForLevel(l)
}
// LogCountForLevelsGreaterThanorEqualTo returns the number of log invocations
// greater than or equal to a given threshold.
func LogCountForLevelsGreaterThanorEqualTo(threshold Threshold) uint64 {
return defaultNotepad.LogCountForLevelsGreaterThanorEqualTo(threshold)
}
// ResetLogCounters resets the invocation counters for all levels.
func ResetLogCounters() {
defaultNotepad.ResetLogCounters()
}

View File

@ -0,0 +1,56 @@
// Copyright © 2016 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package jwalterweatherman
import (
"sync/atomic"
)
type logCounter struct {
counter uint64
}
func (c *logCounter) incr() {
atomic.AddUint64(&c.counter, 1)
}
func (c *logCounter) resetCounter() {
atomic.StoreUint64(&c.counter, 0)
}
func (c *logCounter) getCount() uint64 {
return atomic.LoadUint64(&c.counter)
}
func (c *logCounter) Write(p []byte) (n int, err error) {
c.incr()
return len(p), nil
}
// LogCountForLevel returns the number of log invocations for a given threshold.
func (n *Notepad) LogCountForLevel(l Threshold) uint64 {
return n.logCounters[l].getCount()
}
// LogCountForLevelsGreaterThanorEqualTo returns the number of log invocations
// greater than or equal to a given threshold.
func (n *Notepad) LogCountForLevelsGreaterThanorEqualTo(threshold Threshold) uint64 {
var cnt uint64
for i := int(threshold); i < len(n.logCounters); i++ {
cnt += n.LogCountForLevel(Threshold(i))
}
return cnt
}
// ResetLogCounters resets the invocation counters for all levels.
func (n *Notepad) ResetLogCounters() {
for _, np := range n.logCounters {
np.resetCounter()
}
}

177
vendor/github.com/spf13/jwalterweatherman/notepad.go generated vendored Normal file
View File

@ -0,0 +1,177 @@
// Copyright © 2016 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package jwalterweatherman
import (
"fmt"
"io"
"log"
"os"
)
type Threshold int
const (
LevelTrace Threshold = iota
LevelDebug
LevelInfo
LevelWarn
LevelError
LevelCritical
LevelFatal
)
var prefixes map[Threshold]string = map[Threshold]string{
LevelTrace: "TRACE ",
LevelDebug: "DEBUG ",
LevelInfo: "INFO ",
LevelWarn: "WARN ",
LevelError: "ERROR ",
LevelCritical: "CRITICAL ",
LevelFatal: "FATAL ",
}
// Notepad is where you leave a note !
type Notepad struct {
TRACE *log.Logger
DEBUG *log.Logger
INFO *log.Logger
WARN *log.Logger
ERROR *log.Logger
CRITICAL *log.Logger
FATAL *log.Logger
LOG *log.Logger
FEEDBACK *Feedback
loggers []**log.Logger
logHandle io.Writer
outHandle io.Writer
logThreshold Threshold
stdoutThreshold Threshold
prefix string
flags int
// One per Threshold
logCounters [7]*logCounter
}
// NewNotepad create a new notepad.
func NewNotepad(outThreshold Threshold, logThreshold Threshold, outHandle, logHandle io.Writer, prefix string, flags int) *Notepad {
n := &Notepad{}
n.loggers = append(n.loggers, &n.TRACE, &n.DEBUG, &n.INFO, &n.WARN, &n.ERROR, &n.CRITICAL, &n.FATAL)
n.logHandle = logHandle
n.outHandle = outHandle
n.logThreshold = logThreshold
n.stdoutThreshold = outThreshold
if len(prefix) != 0 {
n.prefix = "[" + prefix + "] "
} else {
n.prefix = ""
}
n.flags = flags
n.LOG = log.New(n.logHandle,
"LOG: ",
n.flags)
n.FEEDBACK = &Feedback{n}
n.init()
return n
}
// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
type Feedback struct {
*Notepad
}
// init create the loggers for each level depending on the notepad thresholds
func (n *Notepad) init() {
bothHandle := io.MultiWriter(n.outHandle, n.logHandle)
for t, logger := range n.loggers {
threshold := Threshold(t)
counter := &logCounter{}
n.logCounters[t] = counter
switch {
case threshold >= n.logThreshold && threshold >= n.stdoutThreshold:
*logger = log.New(io.MultiWriter(counter, bothHandle), n.prefix+prefixes[threshold], n.flags)
case threshold >= n.logThreshold:
*logger = log.New(io.MultiWriter(counter, n.logHandle), n.prefix+prefixes[threshold], n.flags)
case threshold >= n.stdoutThreshold:
*logger = log.New(io.MultiWriter(counter, os.Stdout), n.prefix+prefixes[threshold], n.flags)
default:
*logger = log.New(counter, n.prefix+prefixes[threshold], n.flags)
}
}
}
// SetLogThreshold change the threshold above which messages are written to the
// log file
func (n *Notepad) SetLogThreshold(threshold Threshold) {
n.logThreshold = threshold
n.init()
}
// SetLogOutput change the file where log messages are written
func (n *Notepad) SetLogOutput(handle io.Writer) {
n.logHandle = handle
n.init()
}
// SetStdoutThreshold change the threshold above which messages are written to the
// standard output
func (n *Notepad) SetStdoutThreshold(threshold Threshold) {
n.stdoutThreshold = threshold
n.init()
}
// SetPrefix change the prefix used by the notepad. Prefixes are displayed between
// brackets at the begining of the line. An empty prefix won't be displayed at all.
func (n *Notepad) SetPrefix(prefix string) {
if len(prefix) != 0 {
n.prefix = "[" + prefix + "] "
} else {
n.prefix = ""
}
n.init()
}
// SetFlags choose which flags the logger will display (after prefix and message
// level). See the package log for more informations on this.
func (n *Notepad) SetFlags(flags int) {
n.flags = flags
n.init()
}
// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
func (fb *Feedback) Println(v ...interface{}) {
s := fmt.Sprintln(v...)
fmt.Print(s)
fb.LOG.Output(2, s)
}
// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
func (fb *Feedback) Printf(format string, v ...interface{}) {
s := fmt.Sprintf(format, v...)
fmt.Print(s)
fb.LOG.Output(2, s)
}

View File

@ -1,256 +0,0 @@
// Copyright © 2016 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package jwalterweatherman
import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"sync/atomic"
)
// Level describes the chosen log level between
// debug and critical.
type Level int
type NotePad struct {
Handle io.Writer
Level Level
Prefix string
Logger **log.Logger
counter uint64
}
func (n *NotePad) incr() {
atomic.AddUint64(&n.counter, 1)
}
func (n *NotePad) resetCounter() {
atomic.StoreUint64(&n.counter, 0)
}
func (n *NotePad) getCount() uint64 {
return atomic.LoadUint64(&n.counter)
}
type countingWriter struct {
incrFunc func()
}
func (cw *countingWriter) Write(p []byte) (n int, err error) {
cw.incrFunc()
return 0, nil
}
// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
type Feedback struct{}
const (
LevelTrace Level = iota
LevelDebug
LevelInfo
LevelWarn
LevelError
LevelCritical
LevelFatal
DefaultLogThreshold = LevelWarn
DefaultStdoutThreshold = LevelError
)
var (
TRACE *log.Logger
DEBUG *log.Logger
INFO *log.Logger
WARN *log.Logger
ERROR *log.Logger
CRITICAL *log.Logger
FATAL *log.Logger
LOG *log.Logger
FEEDBACK Feedback
LogHandle io.Writer = ioutil.Discard
OutHandle io.Writer = os.Stdout
BothHandle io.Writer = io.MultiWriter(LogHandle, OutHandle)
NotePads []*NotePad = []*NotePad{trace, debug, info, warn, err, critical, fatal}
trace *NotePad = &NotePad{Level: LevelTrace, Handle: os.Stdout, Logger: &TRACE, Prefix: "TRACE: "}
debug *NotePad = &NotePad{Level: LevelDebug, Handle: os.Stdout, Logger: &DEBUG, Prefix: "DEBUG: "}
info *NotePad = &NotePad{Level: LevelInfo, Handle: os.Stdout, Logger: &INFO, Prefix: "INFO: "}
warn *NotePad = &NotePad{Level: LevelWarn, Handle: os.Stdout, Logger: &WARN, Prefix: "WARN: "}
err *NotePad = &NotePad{Level: LevelError, Handle: os.Stdout, Logger: &ERROR, Prefix: "ERROR: "}
critical *NotePad = &NotePad{Level: LevelCritical, Handle: os.Stdout, Logger: &CRITICAL, Prefix: "CRITICAL: "}
fatal *NotePad = &NotePad{Level: LevelFatal, Handle: os.Stdout, Logger: &FATAL, Prefix: "FATAL: "}
logThreshold Level = DefaultLogThreshold
outputThreshold Level = DefaultStdoutThreshold
)
const (
DATE = log.Ldate
TIME = log.Ltime
SFILE = log.Lshortfile
LFILE = log.Llongfile
MSEC = log.Lmicroseconds
)
var logFlags = DATE | TIME | SFILE
func init() {
SetStdoutThreshold(DefaultStdoutThreshold)
}
// initialize will setup the jWalterWeatherman standard approach of providing the user
// some feedback and logging a potentially different amount based on independent log and output thresholds.
// By default the output has a lower threshold than logged
// Don't use if you have manually set the Handles of the different levels as it will overwrite them.
func initialize() {
BothHandle = io.MultiWriter(LogHandle, OutHandle)
for _, n := range NotePads {
if n.Level < outputThreshold && n.Level < logThreshold {
n.Handle = ioutil.Discard
} else if n.Level >= outputThreshold && n.Level >= logThreshold {
n.Handle = BothHandle
} else if n.Level >= outputThreshold && n.Level < logThreshold {
n.Handle = OutHandle
} else {
n.Handle = LogHandle
}
}
for _, n := range NotePads {
n.Handle = io.MultiWriter(n.Handle, &countingWriter{n.incr})
*n.Logger = log.New(n.Handle, n.Prefix, logFlags)
}
LOG = log.New(LogHandle,
"LOG: ",
logFlags)
}
// Set the log Flags (Available flag: DATE, TIME, SFILE, LFILE and MSEC)
func SetLogFlag(flags int) {
logFlags = flags
initialize()
}
// Level returns the current global log threshold.
func LogThreshold() Level {
return logThreshold
}
// Level returns the current global output threshold.
func StdoutThreshold() Level {
return outputThreshold
}
// Ensures that the level provided is within the bounds of available levels
func levelCheck(level Level) Level {
switch {
case level <= LevelTrace:
return LevelTrace
case level >= LevelFatal:
return LevelFatal
default:
return level
}
}
// Establishes a threshold where anything matching or above will be logged
func SetLogThreshold(level Level) {
logThreshold = levelCheck(level)
initialize()
}
// Establishes a threshold where anything matching or above will be output
func SetStdoutThreshold(level Level) {
outputThreshold = levelCheck(level)
initialize()
}
// Conveniently Sets the Log Handle to a io.writer created for the file behind the given filepath
// Will only append to this file
func SetLogFile(path string) {
file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
CRITICAL.Println("Failed to open log file:", path, err)
os.Exit(-1)
}
INFO.Println("Logging to", file.Name())
LogHandle = file
initialize()
}
// Conveniently Creates a temporary file and sets the Log Handle to a io.writer created for it
func UseTempLogFile(prefix string) {
file, err := ioutil.TempFile(os.TempDir(), prefix)
if err != nil {
CRITICAL.Println(err)
}
INFO.Println("Logging to", file.Name())
LogHandle = file
initialize()
}
// LogCountForLevel returns the number of log invocations for a given level.
func LogCountForLevel(l Level) uint64 {
for _, np := range NotePads {
if np.Level == l {
return np.getCount()
}
}
return 0
}
// LogCountForLevelsGreaterThanorEqualTo returns the number of log invocations
// greater than or equal to a given level threshold.
func LogCountForLevelsGreaterThanorEqualTo(threshold Level) uint64 {
var cnt uint64
for _, np := range NotePads {
if np.Level >= threshold {
cnt += np.getCount()
}
}
return cnt
}
// ResetLogCounters resets the invocation counters for all levels.
func ResetLogCounters() {
for _, np := range NotePads {
np.resetCounter()
}
}
// Disables logging for the entire JWW system
func DiscardLogging() {
LogHandle = ioutil.Discard
initialize()
}
// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
func (fb *Feedback) Println(v ...interface{}) {
s := fmt.Sprintln(v...)
fmt.Print(s)
LOG.Output(2, s)
}
// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
func (fb *Feedback) Printf(format string, v ...interface{}) {
s := fmt.Sprintf(format, v...)
fmt.Print(s)
LOG.Output(2, s)
}

View File

@ -2,10 +2,18 @@ package gojsonschema
import (
"bytes"
"sync"
"text/template"
)
var errorTemplates *template.Template
var errorTemplates errorTemplate = errorTemplate{template.New("errors-new"),sync.RWMutex{}}
// template.Template is not thread-safe for writing, so some locking is done
// sync.RWMutex is used for efficiently locking when new templates are created
type errorTemplate struct {
*template.Template
sync.RWMutex
}
type (
// RequiredError. ErrorDetails: property string
@ -241,14 +249,17 @@ func formatErrorDescription(s string, details ErrorDetails) string {
var descrAsBuffer bytes.Buffer
var err error
if errorTemplates == nil {
errorTemplates = template.New("all-errors")
}
errorTemplates.RLock()
tpl = errorTemplates.Lookup(s)
errorTemplates.RUnlock()
if tpl == nil {
errorTemplates.Lock()
tpl = errorTemplates.New(s)
tpl, err = tpl.Parse(s)
errorTemplates.Unlock()
if err != nil {
return err.Error()
}

View File

@ -101,7 +101,9 @@ func (l *jsonReferenceLoader) JsonReference() (gojsonreference.JsonReference, er
}
func (l *jsonReferenceLoader) LoaderFactory() JSONLoaderFactory {
return &DefaultJSONLoaderFactory{}
return &FileSystemJSONLoaderFactory{
fs: l.fs,
}
}
// NewReferenceLoader returns a JSON reference loader using the given source and the local OS file system.