This is an automated email from the ASF dual-hosted git repository.

tokers pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new 382e1ab  chore: config module. (#77)
382e1ab is described below

commit 382e1ab1017543de72eb2460ed4a92d8e98ac029
Author: Alex Zhang <[email protected]>
AuthorDate: Fri Dec 11 15:34:24 2020 +0800

    chore: config module. (#77)
    
    * chore: config module.
    
    Re designed a config module, supporting initializing from command line 
optoins and files.
    Now this module is not in use. Will be used in the near future.
    
    * chore: changed the minimal resync interval to 30s
---
 cmd/ingress/ingress.go         |  84 +++++++++++++++--------
 conf/config-default.yaml       |  48 +++++++++++++
 pkg/config/config.go           | 102 ++++++++++++++++++++++++++++
 pkg/config/config_test.go      | 149 +++++++++++++++++++++++++++++++++++++++++
 pkg/log/default_logger.go      |  28 ++++----
 pkg/log/default_logger_test.go |  28 ++++----
 pkg/log/logger.go              |  28 ++++----
 pkg/log/logger_test.go         |  28 ++++----
 pkg/log/options.go             |  28 ++++----
 pkg/types/timeduration.go      |  71 ++++++++++++++++++++
 pkg/types/timeduration_test.go |  75 +++++++++++++++++++++
 11 files changed, 572 insertions(+), 97 deletions(-)

diff --git a/cmd/ingress/ingress.go b/cmd/ingress/ingress.go
index 805a016..ab46bc7 100644
--- a/cmd/ingress/ingress.go
+++ b/cmd/ingress/ingress.go
@@ -1,36 +1,49 @@
- // Licensed to the Apache Software Foundation (ASF) under one or more
- // contributor license agreements.  See the NOTICE file distributed with
- // this work for additional information regarding copyright ownership.
- // The ASF licenses this file to You under the Apache License, Version 2.0
- // (the "License"); you may not use this file except in compliance with
- // the License.  You may obtain a copy of the License at
- //
- //     http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 package ingress
 
 import (
-       "flag"
+       "fmt"
        "net/http"
+       "os"
+       "strings"
        "time"
 
-       "github.com/golang/glog"
        api6Informers 
"github.com/gxthrj/apisix-ingress-types/pkg/client/informers/externalversions"
        "github.com/spf13/cobra"
 
        "github.com/api7/ingress-controller/conf"
-       "github.com/api7/ingress-controller/log"
        "github.com/api7/ingress-controller/pkg"
+       "github.com/api7/ingress-controller/pkg/config"
        "github.com/api7/ingress-controller/pkg/ingress/controller"
+       "github.com/api7/ingress-controller/pkg/log"
 )
 
+func dief(template string, args ...interface{}) {
+       if !strings.HasSuffix(template, "\n") {
+               template += "\n"
+       }
+       fmt.Fprintf(os.Stderr, template, args...)
+       os.Exit(1)
+}
+
 // NewIngressCommand creates the ingress sub command for 
apisix-ingress-controller.
 func NewIngressCommand() *cobra.Command {
+       var configPath string
+       cfg := config.NewDefaultConfig()
+
        cmd := &cobra.Command{
                Use:   "ingress [flags]",
                Short: "launch the controller",
@@ -38,9 +51,23 @@ func NewIngressCommand() *cobra.Command {
 
        apisix-ingress-controller ingress --config-path /path/to/config.json`,
                Run: func(cmd *cobra.Command, args []string) {
-                       flag.Parse()
-                       defer glog.Flush()
-                       var logger = log.GetLogger()
+                       if configPath != "" {
+                               c, err := config.NewConfigFromFile(configPath)
+                               if err != nil {
+                                       dief("failed to initialize 
configuration: %s", err)
+                               }
+                               cfg = c
+                       }
+
+                       logger, err := log.NewLogger(
+                               log.WithLogLevel(cfg.LogLevel),
+                               log.WithOutputFile(cfg.LogOutput),
+                       )
+                       if err != nil {
+                               dief("failed to initialize logging: %s", err)
+                       }
+                       log.DefaultLogger = logger
+
                        kubeClientSet := conf.GetKubeClient()
                        apisixClientset := conf.InitApisixClient()
                        sharedInformerFactory := 
api6Informers.NewSharedInformerFactory(apisixClientset, 0)
@@ -71,19 +98,22 @@ func NewIngressCommand() *cobra.Command {
                        }()
 
                        router := pkg.Route()
-                       err := http.ListenAndServe(":8080", router)
+                       err = http.ListenAndServe(":8080", router)
                        if err != nil {
                                logger.Fatal("ListenAndServe: ", err)
                        }
                },
        }
 
-       // TODO: Uncomment these lines.
-       // cmd.PersistentFlags().StringVar(&configPath, "config-path", "", 
"file path for the configuration of apisix-ingress-controller")
-       // cmd.PersistentFlags().StringVar(&conf.Kubeconfig, "kubeconfig", "", 
"Kubernetes configuration file (by default in-cluster configuration will be 
used)")
-       // cmd.PersistentFlags().StringSliceVar(&conf.Etcd.Endpoints, 
"etcd-endpoints", nil, "etcd endpoints")
-       // cmd.PersistentFlags().StringVar(&conf.APISIX.BaseURL, 
"apisix-base-url", "", "the base URL for APISIX instance")
-       // cmd.PersistentFlags().StringVar(&conf.SyslogServer, "syslog-server", 
"", "syslog server address")
+       cmd.PersistentFlags().StringVar(&configPath, "config-path", "", 
"configuration file path for apisix-ingress-controller")
+       cmd.PersistentFlags().StringVar(&cfg.LogLevel, "log-level", "warn", 
"error log level")
+       cmd.PersistentFlags().StringVar(&cfg.LogOutput, "log-output", "stderr", 
"error log output file")
+       cmd.PersistentFlags().StringVar(&cfg.HTTPListen, "http-listen", 
":8080", "the HTTP Server listen address")
+       cmd.PersistentFlags().BoolVar(&cfg.EnableProfiling, "enable-profiling", 
true, "enable profiling via web interface host:port/debug/pprof")
+       cmd.PersistentFlags().StringVar(&cfg.Kubernetes.Kubeconfig, 
"kubeconfig", "", "Kubernetes configuration file (by default in-cluster 
configuration will be used)")
+       
cmd.PersistentFlags().DurationVar(&cfg.Kubernetes.ResyncInterval.Duration, 
"resync-interval", time.Minute, "the controller resync (with Kubernetes) 
interval, the minimum resync interval is 30s")
+       cmd.PersistentFlags().StringVar(&cfg.APISIX.BaseURL, "apisix-base-url", 
"", "the base URL for APISIX admin api / manager api")
+       cmd.PersistentFlags().StringVar(&cfg.APISIX.AdminKey, 
"apisix-admin-key", "", "admin key used for the authorization of APISIX admin 
api / manager api")
 
        return cmd
 }
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
new file mode 100644
index 0000000..d3af0b2
--- /dev/null
+++ b/conf/config-default.yaml
@@ -0,0 +1,48 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# log options
+log_level: "warn"    # the error log level, default is warn, optional values 
are:
+                     # debug
+                     # info
+                     # warn
+                     # error
+                     # panic
+                     # fatal
+log_output: "stderr" # the output file path of error log, default is stderr, 
when
+                     # the file path is "stderr" or "stdout", logs are 
marshalled
+                     # plainly, which is more readable for human; otherwise 
logs
+                     # are marshalled in JSON format, which can be parsed by
+                     # programs easily.
+
+http_listen: ":8080"   # the HTTP Server listen address, default is ":8080"
+enable_profiling: true # enable profileing via web interfaces
+                       # host:port/debug/pprof, default is true.
+
+# Kubernetes related configurations.
+kubernetes:
+  kubeconfig: ""         # the Kubernetes configuration file path, default is
+                         # "", so the in-cluster configuration will be used.
+  resync_interval: "60s" # how long should apisix-ingress-controller
+                         # re-synchronizes with Kubernetes, default is 60s,
+                         # and the minimal resync interval is 30s.
+
+# APISIX related configurations.
+apisix:
+  base_url: "http://host:port/foo/bar/baz"; # the APISIX admin api / manager api
+                                           # base url, it's required.
+  #admin_key: "blahblahblah"               # admin key for the authorization
+                                           # of APISIX admin api / manager api,
+                                           # default is empty.
diff --git a/pkg/config/config.go b/pkg/config/config.go
new file mode 100644
index 0000000..ac26a30
--- /dev/null
+++ b/pkg/config/config.go
@@ -0,0 +1,102 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package config
+
+import (
+       "encoding/json"
+       "errors"
+       "io/ioutil"
+       "strings"
+       "time"
+
+       "github.com/api7/ingress-controller/pkg/types"
+       "gopkg.in/yaml.v2"
+)
+
+const (
+       _minimalResyncInterval = 30 * time.Second
+)
+
+// Config contains all config items which are necessary for
+// apisix-ingress-controller's running.
+type Config struct {
+       LogLevel        string           `json:"log_level" yaml:"log_level"`
+       LogOutput       string           `json:"log_output" yaml:"log_output"`
+       HTTPListen      string           `json:"http_listen" yaml:"http_listen"`
+       EnableProfiling bool             `json:"enable_profiling" 
yaml:"enable_profiling"`
+       Kubernetes      KubernetesConfig `json:"kubernetes" yaml:"kubernetes"`
+       APISIX          APISIXConfig     `json:"apisix" yaml:"apisix"`
+}
+
+// KubernetesConfig contains all Kubernetes related config items.
+type KubernetesConfig struct {
+       Kubeconfig     string             `json:"kubeconfig" yaml:"kubeconfig"`
+       ResyncInterval types.TimeDuration `json:"resync_interval" 
yaml:"resync_interval"`
+}
+
+// APISIXConfig contains all APISIX related config items.
+type APISIXConfig struct {
+       BaseURL string `json:"base_url" yaml:"base_url"`
+       // TODO: Obsolete the plain way to specify admin_key, which is insecure.
+       AdminKey string `json:"admin_key" yaml:"admin_key"`
+}
+
+// NewDefaultConfig creates a Config object which fills all config items with
+// default value.
+func NewDefaultConfig() *Config {
+       return &Config{
+               LogLevel:        "warn",
+               LogOutput:       "stderr",
+               HTTPListen:      ":8080",
+               EnableProfiling: true,
+               Kubernetes: KubernetesConfig{
+                       Kubeconfig:     "", // Use in-cluster configurations.
+                       ResyncInterval: types.TimeDuration{time.Minute},
+               },
+       }
+}
+
+// NewConfigFromFile creates a Config object and fills all config items 
according
+// to the configuration file. The file can be in JSON/YAML format, which will 
be
+// distinguished according to the file suffix.
+func NewConfigFromFile(filename string) (*Config, error) {
+       cfg := NewDefaultConfig()
+       data, err := ioutil.ReadFile(filename)
+       if err != nil {
+               return nil, err
+       }
+
+       if strings.HasSuffix(filename, ".yaml") || strings.HasSuffix(filename, 
".yml") {
+               err = yaml.Unmarshal(data, cfg)
+       } else {
+               err = json.Unmarshal(data, cfg)
+       }
+
+       if err != nil {
+               return nil, err
+       }
+       return cfg, nil
+}
+
+// Validate validates whether the Config is right.
+func (cfg *Config) Validate() error {
+       if cfg.Kubernetes.ResyncInterval.Duration < _minimalResyncInterval {
+               return errors.New("controller resync interval too small")
+       }
+       if cfg.APISIX.BaseURL == "" {
+               return errors.New("apisix base url is required")
+       }
+       return nil
+}
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
new file mode 100644
index 0000000..483d5f0
--- /dev/null
+++ b/pkg/config/config_test.go
@@ -0,0 +1,149 @@
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You under the Apache License, Version 2.0
+//  (the "License"); you may not use this file except in compliance with
+//  the License.  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+package config
+
+import (
+       "encoding/json"
+       "io/ioutil"
+       "os"
+       "testing"
+       "time"
+
+       "github.com/stretchr/testify/assert"
+
+       "github.com/api7/ingress-controller/pkg/types"
+)
+
+func TestNewConfigFromFile(t *testing.T) {
+       cfg := &Config{
+               LogLevel:        "warn",
+               LogOutput:       "stdout",
+               HTTPListen:      ":9090",
+               EnableProfiling: true,
+               Kubernetes: KubernetesConfig{
+                       ResyncInterval: types.TimeDuration{time.Hour},
+                       Kubeconfig:     "/path/to/foo/baz",
+               },
+               APISIX: APISIXConfig{
+                       BaseURL:  "http://127.0.0.1:8080/apisix";,
+                       AdminKey: "123456",
+               },
+       }
+
+       jsonData, err := json.Marshal(cfg)
+       assert.Nil(t, err, "failed to marshal config to json: %s", err)
+
+       tmpJSON, err := ioutil.TempFile("/tmp", "config-*.json")
+       assert.Nil(t, err, "failed to create temporary json configuration file: 
", err)
+       defer os.Remove(tmpJSON.Name())
+
+       _, err = tmpJSON.Write(jsonData)
+       assert.Nil(t, err, "failed to write json data: ", err)
+       tmpJSON.Close()
+
+       newCfg, err := NewConfigFromFile(tmpJSON.Name())
+       assert.Nil(t, err, "failed to new config from file: ", err)
+       assert.Nil(t, newCfg.Validate(), "failed to validate config")
+
+       assert.Equal(t, cfg, newCfg, "bad configuration")
+
+       // We constrcuts yaml data manually instead of using yaml.Marshal since
+       // types.TimeDuration doesn't have a `yaml:",inline"` tag, if we add it,
+       // error ",inline needs a struct value field" will be reported.
+       // I don't know why.
+       yamlData := `
+log_level: warn
+log_output: stdout
+http_listen: :9090
+enable_profiling: true
+kubernetes:
+  kubeconfig: /path/to/foo/baz
+  resync_interval: 1h0m0s
+apisix:
+  base_url: http://127.0.0.1:8080/apisix
+  admin_key: "123456"
+`
+       tmpYAML, err := ioutil.TempFile("/tmp", "config-*.yaml")
+       assert.Nil(t, err, "failed to create temporary yaml configuration file: 
", err)
+       defer os.Remove(tmpYAML.Name())
+
+       _, err = tmpYAML.Write([]byte(yamlData))
+       assert.Nil(t, err, "failed to write yaml data: ", err)
+       tmpYAML.Close()
+
+       newCfg, err = NewConfigFromFile(tmpYAML.Name())
+       assert.Nil(t, err, "failed to new config from file: ", err)
+       assert.Nil(t, newCfg.Validate(), "failed to validate config")
+
+       assert.Equal(t, cfg, newCfg, "bad configuration")
+}
+
+func TestConfigDefaultValue(t *testing.T) {
+       yamlData := `
+apisix:
+  base_url: http://127.0.0.1:8080/apisix
+`
+       tmpYAML, err := ioutil.TempFile("/tmp", "config-*.yaml")
+       assert.Nil(t, err, "failed to create temporary yaml configuration file: 
", err)
+       defer os.Remove(tmpYAML.Name())
+
+       _, err = tmpYAML.Write([]byte(yamlData))
+       assert.Nil(t, err, "failed to write yaml data: ", err)
+       tmpYAML.Close()
+
+       newCfg, err := NewConfigFromFile(tmpYAML.Name())
+       assert.Nil(t, err, "failed to new config from file: ", err)
+       assert.Nil(t, newCfg.Validate(), "failed to validate config")
+
+       defaultCfg := NewDefaultConfig()
+       defaultCfg.APISIX.BaseURL = "http://127.0.0.1:8080/apisix";
+
+       assert.Equal(t, defaultCfg, newCfg, "bad configuration")
+}
+
+func TestConfigInvalidation(t *testing.T) {
+       yamlData := ``
+       tmpYAML, err := ioutil.TempFile("/tmp", "config-*.yaml")
+       assert.Nil(t, err, "failed to create temporary yaml configuration file: 
", err)
+       defer os.Remove(tmpYAML.Name())
+
+       _, err = tmpYAML.Write([]byte(yamlData))
+       assert.Nil(t, err, "failed to write yaml data: ", err)
+       tmpYAML.Close()
+
+       newCfg, err := NewConfigFromFile(tmpYAML.Name())
+       assert.Nil(t, err, "failed to new config from file: ", err)
+       err = newCfg.Validate()
+       assert.Equal(t, err.Error(), "apisix base url is required", "bad error: 
", err)
+
+       yamlData = `
+kubernetes:
+  resync_interval: 15s
+apisix:
+  base_url: http://127.0.0.1:1234/apisix
+`
+       tmpYAML, err = ioutil.TempFile("/tmp", "config-*.yaml")
+       assert.Nil(t, err, "failed to create temporary yaml configuration file: 
", err)
+       defer os.Remove(tmpYAML.Name())
+
+       _, err = tmpYAML.Write([]byte(yamlData))
+       assert.Nil(t, err, "failed to write yaml data: ", err)
+       tmpYAML.Close()
+
+       newCfg, err = NewConfigFromFile(tmpYAML.Name())
+       assert.Nil(t, err, "failed to new config from file: ", err)
+       err = newCfg.Validate()
+       assert.Equal(t, err.Error(), "controller resync interval too small", 
"bad error: ", err)
+}
diff --git a/pkg/log/default_logger.go b/pkg/log/default_logger.go
index c318c22..4c5b834 100644
--- a/pkg/log/default_logger.go
+++ b/pkg/log/default_logger.go
@@ -1,17 +1,17 @@
- // Licensed to the Apache Software Foundation (ASF) under one or more
- // contributor license agreements.  See the NOTICE file distributed with
- // this work for additional information regarding copyright ownership.
- // The ASF licenses this file to You under the Apache License, Version 2.0
- // (the "License"); you may not use this file except in compliance with
- // the License.  You may obtain a copy of the License at
- //
- //     http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 package log
 
 import "go.uber.org/zap/zapcore"
diff --git a/pkg/log/default_logger_test.go b/pkg/log/default_logger_test.go
index ba7fed0..8bee9fa 100644
--- a/pkg/log/default_logger_test.go
+++ b/pkg/log/default_logger_test.go
@@ -1,17 +1,17 @@
- // Licensed to the Apache Software Foundation (ASF) under one or more
- // contributor license agreements.  See the NOTICE file distributed with
- // this work for additional information regarding copyright ownership.
- // The ASF licenses this file to You under the Apache License, Version 2.0
- // (the "License"); you may not use this file except in compliance with
- // the License.  You may obtain a copy of the License at
- //
- //     http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 package log
 
 import (
diff --git a/pkg/log/logger.go b/pkg/log/logger.go
index 5f7c55d..44eb392 100644
--- a/pkg/log/logger.go
+++ b/pkg/log/logger.go
@@ -1,17 +1,17 @@
- // Licensed to the Apache Software Foundation (ASF) under one or more
- // contributor license agreements.  See the NOTICE file distributed with
- // this work for additional information regarding copyright ownership.
- // The ASF licenses this file to You under the Apache License, Version 2.0
- // (the "License"); you may not use this file except in compliance with
- // the License.  You may obtain a copy of the License at
- //
- //     http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 package log
 
 import (
diff --git a/pkg/log/logger_test.go b/pkg/log/logger_test.go
index a66e64b..ce1adfe 100644
--- a/pkg/log/logger_test.go
+++ b/pkg/log/logger_test.go
@@ -1,17 +1,17 @@
- // Licensed to the Apache Software Foundation (ASF) under one or more
- // contributor license agreements.  See the NOTICE file distributed with
- // this work for additional information regarding copyright ownership.
- // The ASF licenses this file to You under the Apache License, Version 2.0
- // (the "License"); you may not use this file except in compliance with
- // the License.  You may obtain a copy of the License at
- //
- //     http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 package log
 
 import (
diff --git a/pkg/log/options.go b/pkg/log/options.go
index af09fda..695f799 100644
--- a/pkg/log/options.go
+++ b/pkg/log/options.go
@@ -1,17 +1,17 @@
- // Licensed to the Apache Software Foundation (ASF) under one or more
- // contributor license agreements.  See the NOTICE file distributed with
- // this work for additional information regarding copyright ownership.
- // The ASF licenses this file to You under the Apache License, Version 2.0
- // (the "License"); you may not use this file except in compliance with
- // the License.  You may obtain a copy of the License at
- //
- //     http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
 package log
 
 import (
diff --git a/pkg/types/timeduration.go b/pkg/types/timeduration.go
new file mode 100644
index 0000000..41127fa
--- /dev/null
+++ b/pkg/types/timeduration.go
@@ -0,0 +1,71 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package types
+
+import (
+       "encoding/json"
+       "time"
+)
+
+// TimeDuration is yet another time.Duration but implements json.Unmarshaler
+// and json.Marshaler, yaml.Unmarshaler and yaml.Marshaler interfaces so one
+// can use "1h", "5s" and etc in their json/yaml configurations.
+//
+// Note the format to represent time is same as time.Duration.
+// See the comments about time.ParseDuration for more details.
+type TimeDuration struct {
+       time.Duration `json:",inline"`
+}
+
+func (d *TimeDuration) MarshalJSON() ([]byte, error) {
+       return json.Marshal(d.Duration.String())
+}
+
+func (d *TimeDuration) UnmarshalJSON(data []byte) error {
+       var value interface{}
+       if err := json.Unmarshal(data, &value); err != nil {
+               return err
+       }
+       switch value.(type) {
+       case float64:
+               d.Duration = time.Duration(value.(float64))
+       case string:
+               dur, err := time.ParseDuration(value.(string))
+               if err != nil {
+                       return err
+               }
+               d.Duration = dur
+       default:
+               panic("unknown type")
+       }
+       return nil
+}
+
+func (d *TimeDuration) MarshalYAML() (interface{}, error) {
+       return d.Duration.String(), nil
+}
+
+func (d *TimeDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
+       var s string
+       if err := unmarshal(&s); err != nil {
+               return err
+       }
+       dur, err := time.ParseDuration(s)
+       if err != nil {
+               return err
+       }
+       d.Duration = dur
+       return nil
+}
diff --git a/pkg/types/timeduration_test.go b/pkg/types/timeduration_test.go
new file mode 100644
index 0000000..aeab17f
--- /dev/null
+++ b/pkg/types/timeduration_test.go
@@ -0,0 +1,75 @@
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You under the Apache License, Version 2.0
+//  (the "License"); you may not use this file except in compliance with
+//  the License.  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+package types
+
+import (
+       "encoding/json"
+       "testing"
+       "time"
+
+       "github.com/stretchr/testify/assert"
+       "gopkg.in/yaml.v2"
+)
+
+type structure1 struct {
+       Interval TimeDuration `json:"interval" yaml:"interval"`
+       Name     string       `json:"name" yaml:"name"`
+}
+
+func TestTimeDurationMarshalJSON(t *testing.T) {
+       value := &structure1{
+               Interval: TimeDuration{15 * time.Second},
+               Name:     "alex",
+       }
+       data, err := json.Marshal(value)
+       assert.Nil(t, err, "failed to marshal value: %s", err)
+       assert.Contains(t, string(data), "15s", "bad marshalled json: %s", 
string(data))
+}
+
+func TestTimeDurationUnmarshalJSON(t *testing.T) {
+       data := `
+               {
+                       "interval": "3m",
+                       "name": "alex"
+               }`
+
+       var value structure1
+       err := json.Unmarshal([]byte(data), &value)
+       assert.Nil(t, err, "failed to unmarshal data to structure1: %v", err)
+       assert.Equal(t, value.Name, "alex", "bad name: %s", value.Name)
+       assert.Equal(t, value.Interval, TimeDuration{3 * time.Minute}, "bad 
interval: %v", value.Interval)
+}
+
+func TestTimeDurationMarshalYAML(t *testing.T) {
+       value := &structure1{
+               Interval: TimeDuration{15 * time.Second},
+               Name:     "alex",
+       }
+       data, err := yaml.Marshal(value)
+       assert.Nil(t, err, "failed to marshal value: %s", err)
+       assert.Contains(t, string(data), "15s", "bad marshalled json: %s", 
string(data))
+}
+
+func TestTimeDurationUnmarshalYAML(t *testing.T) {
+       data := `
+interval: 3m
+name: alex
+`
+       var value structure1
+       err := yaml.Unmarshal([]byte(data), &value)
+       assert.Nil(t, err, "failed to unmarshal data to structure1: %v", err)
+       assert.Equal(t, value.Name, "alex", "bad name: %s", value.Name)
+       assert.Equal(t, value.Interval, TimeDuration{3 * time.Minute}, "bad 
interval: %v", value.Interval)
+}

Reply via email to