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