mirror of
https://github.com/redhat-developer/odo.git
synced 2025-10-19 03:06:19 +03:00
Centralize environment configuration (#6293)
* Define central config * Use envConfig in GenericRun for segment parameters * Pass the env config into the context passed to CLI methods * Use PodmanCmd and DockerCmd from context * Remove tests now that ODO_DISABLE_TELEMETRY is checked for a bool value * deploy.Deploy: Use values from ctx instead of parameters + use FS from DI * dev.Start: Use values from ctx instead of parameters * image.Build*: Use values from ctx instead of parameters * Use telemetry file from context * Pass ctx to segment.getTelemetryForDevfileRegistry * Use ctx in getTelemetryForDevfileRegistry * Call IsTelemetryEnabled once and use scontext.GetTelemetryStatus after * Fix unit tests * Use envConfig in segment.IsTelemetryEnabled * Define TelemetryCaller constant in test helper * IsTrackingConsentEnabled: get value from envconfig instead of env * Use ctx instead of GLOBALODOCONFIG * Place ODO_EXPERIMENTAL_MODE in configuration * Use maintained envconfig package * Define default values when exist * Document accepted boolean values
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -171,11 +172,12 @@ func main() {
|
||||
if len(args) == 0 {
|
||||
fmt.Print(command.Usage())
|
||||
} else {
|
||||
ctx := context.Background()
|
||||
switch args[0] {
|
||||
case "reference":
|
||||
fmt.Print(referencePrinter(cli.NewCmdOdo(cli.OdoRecommendedName, cli.OdoRecommendedName), 0))
|
||||
fmt.Print(referencePrinter(cli.NewCmdOdo(ctx, cli.OdoRecommendedName, cli.OdoRecommendedName), 0))
|
||||
case "structure":
|
||||
fmt.Print(commandPrinter(cli.NewCmdOdo(cli.OdoRecommendedName, cli.OdoRecommendedName), 0))
|
||||
fmt.Print(commandPrinter(cli.NewCmdOdo(ctx, cli.OdoRecommendedName, cli.OdoRecommendedName), 0))
|
||||
default:
|
||||
fmt.Print(command.Usage())
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/posener/complete"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli"
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/version"
|
||||
@@ -13,16 +18,24 @@ import (
|
||||
"github.com/redhat-developer/odo/pkg/odo/util/completion"
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
segment "github.com/redhat-developer/odo/pkg/segment/context"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a context ready for receiving telemetry data
|
||||
// and save into it configuration based on environment variables
|
||||
ctx := segment.NewContext(context.Background())
|
||||
envConfig, err := config.GetConfiguration()
|
||||
if err != nil {
|
||||
util.LogErrorAndExit(err, "")
|
||||
}
|
||||
ctx = envcontext.WithEnvConfig(ctx, *envConfig)
|
||||
|
||||
// create the complete command
|
||||
klog.InitFlags(nil)
|
||||
|
||||
root := cli.NewCmdOdo(cli.OdoRecommendedName, cli.OdoRecommendedName)
|
||||
root := cli.NewCmdOdo(ctx, cli.OdoRecommendedName, cli.OdoRecommendedName)
|
||||
rootCmp := createCompletion(root)
|
||||
cmp := complete.New("odo", rootCmp)
|
||||
|
||||
@@ -41,7 +54,7 @@ func main() {
|
||||
// parse the flags but hack around to avoid exiting with error code 2 on help
|
||||
flag.CommandLine.Init(os.Args[0], flag.ContinueOnError)
|
||||
args := os.Args[1:]
|
||||
if err := flag.CommandLine.Parse(args); err != nil {
|
||||
if err = flag.CommandLine.Parse(args); err != nil {
|
||||
if err == flag.ErrHelp {
|
||||
os.Exit(0)
|
||||
}
|
||||
@@ -55,7 +68,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := preference.NewClient()
|
||||
cfg, err := preference.NewClient(ctx)
|
||||
if err != nil {
|
||||
util.LogErrorAndExit(err, "")
|
||||
}
|
||||
@@ -67,7 +80,7 @@ func main() {
|
||||
updateInfo := make(chan string)
|
||||
go version.GetLatestReleaseInfo(updateInfo)
|
||||
|
||||
util.LogErrorAndExit(root.ExecuteContext(segment.NewContext(context.Background())), "")
|
||||
util.LogErrorAndExit(root.ExecuteContext(ctx), "")
|
||||
select {
|
||||
case message := <-updateInfo:
|
||||
log.Info(message)
|
||||
@@ -75,7 +88,7 @@ func main() {
|
||||
klog.V(4).Info("Could not get the latest release information in time. Never mind, exiting gracefully :)")
|
||||
}
|
||||
} else {
|
||||
util.LogErrorAndExit(root.ExecuteContext(segment.NewContext(context.Background())), "")
|
||||
util.LogErrorAndExit(root.ExecuteContext(ctx), "")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,4 +190,6 @@ Options here are mostly used for debugging and testing `odo` behavior.
|
||||
| `DEVFILE_PROXY` | Integration tests will use this address as Devfile registry instead of `registry.stage.devfile.io` | v3.0.0-beta3 | `my-registry.example.com` |
|
||||
| `TELEMETRY_CALLER` | Caller identifier passed to [telemetry](https://github.com/redhat-developer/odo/blob/main/USAGE_DATA.md). Case-insensitive. Acceptable values: `vscode`, `intellij`, `jboss`. | v3.1.0 | `intellij` |
|
||||
| `ODO_TRACKING_CONSENT` | Useful for controlling [telemetry](https://github.com/redhat-developer/odo/blob/main/USAGE_DATA.md). Acceptable values: `yes` ([enables telemetry](https://github.com/redhat-developer/odo/blob/main/USAGE_DATA.md) and skips consent prompt), `no` (disables telemetry and consent prompt). Takes precedence over the [`ConsentTelemetry`](#preference-key-table) preference. | v3.2.0 | `yes` |
|
||||
| `ODO_EXPERIMENTAL_MODE` | Whether to enable experimental features. See [Experimental Mode](../user-guides/advanced/experimental-mode) for more details. Acceptable values: `true` | v3.3.0 | `true` |
|
||||
| `ODO_EXPERIMENTAL_MODE` | Whether to enable experimental features. See [Experimental Mode](../user-guides/advanced/experimental-mode) for more details. Acceptable values: boolean values<sup>(1)</sup> | v3.3.0 | `true` |
|
||||
|
||||
(1) Accepted boolean values are: `1`, `t`, `T`, `TRUE`, `true`, `True`, `0`, `f`, `F`, `FALSE`, `false`, `False`.
|
||||
1
go.mod
1
go.mod
@@ -39,6 +39,7 @@ require (
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
|
||||
github.com/securego/gosec/v2 v2.14.0
|
||||
github.com/segmentio/backo-go v1.0.1-0.20200129164019-23eae7c10bd3
|
||||
github.com/sethvargo/go-envconfig v0.8.2
|
||||
github.com/spf13/afero v1.6.0
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
|
||||
5
go.sum
5
go.sum
@@ -815,6 +815,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
@@ -883,8 +884,6 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
@@ -1052,6 +1051,8 @@ github.com/segmentio/backo-go v1.0.1-0.20200129164019-23eae7c10bd3/go.mod h1:9/R
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sethvargo/go-envconfig v0.8.2 h1:DDUVuG21RMgeB/bn4leclUI/837y6cQCD4w8hb5797k=
|
||||
github.com/sethvargo/go-envconfig v0.8.2/go.mod h1:Iz1Gy1Sf3T64TQlJSvee81qDhf7YIlt8GMUX6yyNFs0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package alizer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -26,9 +27,9 @@ func NewAlizerClient(registryClient registry.Client) *Alizer {
|
||||
|
||||
// DetectFramework uses the alizer library in order to detect the devfile
|
||||
// to use depending on the files in the path
|
||||
func (o *Alizer) DetectFramework(path string) (recognizer.DevFileType, api.Registry, error) {
|
||||
func (o *Alizer) DetectFramework(ctx context.Context, path string) (recognizer.DevFileType, api.Registry, error) {
|
||||
types := []recognizer.DevFileType{}
|
||||
components, err := o.registryClient.ListDevfileStacks("", "", "", false)
|
||||
components, err := o.registryClient.ListDevfileStacks(ctx, "", "", "", false)
|
||||
if err != nil {
|
||||
return recognizer.DevFileType{}, api.Registry{}, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package alizer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
@@ -113,10 +114,11 @@ func TestDetectFramework(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
registryClient := registry.NewMockClient(ctrl)
|
||||
registryClient.EXPECT().ListDevfileStacks("", "", "", false).Return(list, nil)
|
||||
ctx := context.Background()
|
||||
registryClient.EXPECT().ListDevfileStacks(ctx, "", "", "", false).Return(list, nil)
|
||||
alizerClient := NewAlizerClient(registryClient)
|
||||
// Run function DetectFramework
|
||||
detected, registry, err := alizerClient.DetectFramework(tt.args.path)
|
||||
detected, registry, err := alizerClient.DetectFramework(ctx, tt.args.path)
|
||||
|
||||
if !tt.wantErr == (err != nil) {
|
||||
t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package alizer
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redhat-developer/alizer/go/pkg/apis/recognizer"
|
||||
"github.com/redhat-developer/odo/pkg/api"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
DetectFramework(path string) (recognizer.DevFileType, api.Registry, error)
|
||||
DetectFramework(ctx context.Context, path string) (recognizer.DevFileType, api.Registry, error)
|
||||
DetectName(path string) (string, error)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package alizer
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
@@ -36,9 +37,9 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
||||
}
|
||||
|
||||
// DetectFramework mocks base method.
|
||||
func (m *MockClient) DetectFramework(path string) (recognizer.DevFileType, api.Registry, error) {
|
||||
func (m *MockClient) DetectFramework(ctx context.Context, path string) (recognizer.DevFileType, api.Registry, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DetectFramework", path)
|
||||
ret := m.ctrl.Call(m, "DetectFramework", ctx, path)
|
||||
ret0, _ := ret[0].(recognizer.DevFileType)
|
||||
ret1, _ := ret[1].(api.Registry)
|
||||
ret2, _ := ret[2].(error)
|
||||
@@ -46,9 +47,9 @@ func (m *MockClient) DetectFramework(path string) (recognizer.DevFileType, api.R
|
||||
}
|
||||
|
||||
// DetectFramework indicates an expected call of DetectFramework.
|
||||
func (mr *MockClientMockRecorder) DetectFramework(path interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) DetectFramework(ctx, path interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetectFramework", reflect.TypeOf((*MockClient)(nil).DetectFramework), path)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetectFramework", reflect.TypeOf((*MockClient)(nil).DetectFramework), ctx, path)
|
||||
}
|
||||
|
||||
// DetectName mocks base method.
|
||||
|
||||
29
pkg/config/config.go
Normal file
29
pkg/config/config.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sethvargo/go-envconfig"
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
DevfileProxy *string `env:"DEVFILE_PROXY,noinit"`
|
||||
DockerCmd string `env:"DOCKER_CMD,default=docker"`
|
||||
Globalodoconfig *string `env:"GLOBALODOCONFIG,noinit"`
|
||||
OdoDebugTelemetryFile *string `env:"ODO_DEBUG_TELEMETRY_FILE,noinit"`
|
||||
OdoDisableTelemetry *bool `env:"ODO_DISABLE_TELEMETRY,noinit"`
|
||||
OdoLogLevel *int `env:"ODO_LOG_LEVEL,noinit"`
|
||||
OdoTrackingConsent *string `env:"ODO_TRACKING_CONSENT,noinit"`
|
||||
PodmanCmd string `env:"PODMAN_CMD,default=podman"`
|
||||
TelemetryCaller string `env:"TELEMETRY_CALLER,default="`
|
||||
OdoExperimentalMode bool `env:"ODO_EXPERIMENTAL_MODE,default=false"`
|
||||
}
|
||||
|
||||
func GetConfiguration() (*Configuration, error) {
|
||||
var s Configuration
|
||||
err := envconfig.Process(context.Background(), &s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
54
pkg/config/config_test.go
Normal file
54
pkg/config/config_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultValues(t *testing.T) {
|
||||
cfg, err := GetConfiguration()
|
||||
if err != nil {
|
||||
t.Errorf("Error is not expected: %v", err)
|
||||
}
|
||||
|
||||
checkDefaultStringValue(t, "DockerCmd", cfg.DockerCmd, "docker")
|
||||
checkDefaultStringValue(t, "PodmanCmd", cfg.PodmanCmd, "podman")
|
||||
checkDefaultStringValue(t, "TelemetryCaller", cfg.TelemetryCaller, "")
|
||||
checkDefaultBoolValue(t, "OdoExperimentalMode", cfg.OdoExperimentalMode, false)
|
||||
|
||||
// Use noinit to set non initialized value as nil instead of zero-value
|
||||
checkNilString(t, "DevfileProxy", cfg.DevfileProxy)
|
||||
checkNilString(t, "Globalodoconfig", cfg.Globalodoconfig)
|
||||
checkNilString(t, "Globalodoconfig", cfg.Globalodoconfig)
|
||||
checkNilString(t, "OdoDebugTelemetryFile", cfg.OdoDebugTelemetryFile)
|
||||
checkNilBool(t, "OdoDisableTelemetry", cfg.OdoDisableTelemetry)
|
||||
checkNilString(t, "OdoTrackingConsent", cfg.OdoTrackingConsent)
|
||||
|
||||
}
|
||||
|
||||
func checkDefaultStringValue(t *testing.T, fieldName string, field string, def string) {
|
||||
if field != def {
|
||||
t.Errorf("default value for %q should be %q but is %q", fieldName, def, field)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func checkDefaultBoolValue(t *testing.T, fieldName string, field bool, def bool) {
|
||||
if field != def {
|
||||
t.Errorf("default value for %q should be %v but is %v", fieldName, def, field)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func checkNilString(t *testing.T, fieldName string, field *string) {
|
||||
if field != nil {
|
||||
t.Errorf("value for non specified env var %q should be nil but is %q", fieldName, *field)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func checkNilBool(t *testing.T, fieldName string, field *bool) {
|
||||
if field != nil {
|
||||
t.Errorf("value for non specified env var %q should be nil but is %v", fieldName, *field)
|
||||
|
||||
}
|
||||
}
|
||||
25
pkg/config/context/context.go
Normal file
25
pkg/config/context/context.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
)
|
||||
|
||||
type contextKey struct{}
|
||||
|
||||
var key = contextKey{}
|
||||
|
||||
// WithEnvConfig sets the environment configuration in ctx
|
||||
func WithEnvConfig(ctx context.Context, val config.Configuration) context.Context {
|
||||
return context.WithValue(ctx, key, val)
|
||||
}
|
||||
|
||||
// GetEnvConfig returns the environment configuration from ctx
|
||||
func GetEnvConfig(ctx context.Context) config.Configuration {
|
||||
value := ctx.Value(key)
|
||||
if cast, ok := value.(config.Configuration); ok {
|
||||
return cast
|
||||
}
|
||||
panic("GetEnvConfig can be called only after WithEnvConfig has been called")
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
"github.com/devfile/library/pkg/devfile/parser"
|
||||
@@ -11,27 +13,38 @@ import (
|
||||
"github.com/redhat-developer/odo/pkg/kclient"
|
||||
odolabels "github.com/redhat-developer/odo/pkg/labels"
|
||||
"github.com/redhat-developer/odo/pkg/libdevfile"
|
||||
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
|
||||
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
|
||||
)
|
||||
|
||||
type DeployClient struct {
|
||||
kubeClient kclient.ClientInterface
|
||||
fs filesystem.Filesystem
|
||||
}
|
||||
|
||||
var _ Client = (*DeployClient)(nil)
|
||||
|
||||
func NewDeployClient(kubeClient kclient.ClientInterface) *DeployClient {
|
||||
func NewDeployClient(kubeClient kclient.ClientInterface, fs filesystem.Filesystem) *DeployClient {
|
||||
return &DeployClient{
|
||||
kubeClient: kubeClient,
|
||||
fs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *DeployClient) Deploy(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, appName string, componentName string) error {
|
||||
deployHandler := newDeployHandler(fs, devfileObj, path, o.kubeClient, appName, componentName)
|
||||
return libdevfile.Deploy(devfileObj, deployHandler)
|
||||
func (o *DeployClient) Deploy(ctx context.Context) error {
|
||||
var (
|
||||
devfileObj = odocontext.GetDevfileObj(ctx)
|
||||
devfilePath = odocontext.GetDevfilePath(ctx)
|
||||
path = filepath.Dir(devfilePath)
|
||||
componentName = odocontext.GetComponentName(ctx)
|
||||
appName = odocontext.GetApplication(ctx)
|
||||
)
|
||||
deployHandler := newDeployHandler(ctx, o.fs, *devfileObj, path, o.kubeClient, appName, componentName)
|
||||
return libdevfile.Deploy(*devfileObj, deployHandler)
|
||||
}
|
||||
|
||||
type deployHandler struct {
|
||||
ctx context.Context
|
||||
fs filesystem.Filesystem
|
||||
devfileObj parser.DevfileObj
|
||||
path string
|
||||
@@ -42,8 +55,9 @@ type deployHandler struct {
|
||||
|
||||
var _ libdevfile.Handler = (*deployHandler)(nil)
|
||||
|
||||
func newDeployHandler(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, kubeClient kclient.ClientInterface, appName string, componentName string) *deployHandler {
|
||||
func newDeployHandler(ctx context.Context, fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, kubeClient kclient.ClientInterface, appName string, componentName string) *deployHandler {
|
||||
return &deployHandler{
|
||||
ctx: ctx,
|
||||
fs: fs,
|
||||
devfileObj: devfileObj,
|
||||
path: path,
|
||||
@@ -55,7 +69,7 @@ func newDeployHandler(fs filesystem.Filesystem, devfileObj parser.DevfileObj, pa
|
||||
|
||||
// ApplyImage builds and pushes the OCI image to be used on Kubernetes
|
||||
func (o *deployHandler) ApplyImage(img v1alpha2.Component) error {
|
||||
return image.BuildPushSpecificImage(o.fs, o.path, img, true)
|
||||
return image.BuildPushSpecificImage(o.ctx, o.fs, img, true)
|
||||
}
|
||||
|
||||
// ApplyKubernetes applies inline Kubernetes YAML from the devfile.yaml file
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"github.com/devfile/library/pkg/devfile/parser"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
|
||||
"context"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
// Deploy resources from a devfile located in path, for the specified appName.
|
||||
// The filesystem specified is used to download and store the Dockerfiles needed to build the necessary container images,
|
||||
// in case such Dockerfiles are referenced as remote URLs in the Devfile.
|
||||
Deploy(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, appName string, componentName string) error
|
||||
Deploy(ctx context.Context) error
|
||||
}
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
parser "github.com/devfile/library/pkg/devfile/parser"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
filesystem "github.com/redhat-developer/odo/pkg/testingutil/filesystem"
|
||||
)
|
||||
|
||||
// MockClient is a mock of Client interface.
|
||||
@@ -36,15 +35,15 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
||||
}
|
||||
|
||||
// Deploy mocks base method.
|
||||
func (m *MockClient) Deploy(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path, appName, componentName string) error {
|
||||
func (m *MockClient) Deploy(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Deploy", fs, devfileObj, path, appName, componentName)
|
||||
ret := m.ctrl.Call(m, "Deploy", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Deploy indicates an expected call of Deploy.
|
||||
func (mr *MockClientMockRecorder) Deploy(fs, devfileObj, path, appName, componentName interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) Deploy(ctx interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockClient)(nil).Deploy), fs, devfileObj, path, appName, componentName)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deploy", reflect.TypeOf((*MockClient)(nil).Deploy), ctx)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/binding"
|
||||
"github.com/redhat-developer/odo/pkg/devfile"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"github.com/redhat-developer/odo/pkg/sync"
|
||||
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
|
||||
|
||||
"github.com/devfile/library/pkg/devfile/parser"
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/devfile/adapters"
|
||||
@@ -61,23 +61,26 @@ func NewDevClient(
|
||||
|
||||
func (o *DevClient) Start(
|
||||
ctx context.Context,
|
||||
devfileObj parser.DevfileObj,
|
||||
componentName string,
|
||||
path string,
|
||||
devfilePath string,
|
||||
out io.Writer,
|
||||
errOut io.Writer,
|
||||
options StartOptions,
|
||||
) error {
|
||||
klog.V(4).Infoln("Creating new adapter")
|
||||
|
||||
var (
|
||||
devfileObj = odocontext.GetDevfileObj(ctx)
|
||||
devfilePath = odocontext.GetDevfilePath(ctx)
|
||||
path = filepath.Dir(devfilePath)
|
||||
componentName = odocontext.GetComponentName(ctx)
|
||||
)
|
||||
|
||||
adapter := component.NewKubernetesAdapter(
|
||||
o.kubernetesClient, o.prefClient, o.portForwardClient, o.bindingClient, o.syncClient, o.execClient,
|
||||
component.AdapterContext{
|
||||
ComponentName: componentName,
|
||||
Context: path,
|
||||
AppName: odocontext.GetApplication(ctx),
|
||||
Devfile: devfileObj,
|
||||
Devfile: *devfileObj,
|
||||
FS: o.filesystem,
|
||||
})
|
||||
|
||||
@@ -93,7 +96,7 @@ func (o *DevClient) Start(
|
||||
|
||||
klog.V(4).Infoln("Creating inner-loop resources for the component")
|
||||
componentStatus := watch.ComponentStatus{}
|
||||
err := adapter.Push(pushParameters, &componentStatus)
|
||||
err := adapter.Push(ctx, pushParameters, &componentStatus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -106,7 +109,7 @@ func (o *DevClient) Start(
|
||||
ApplicationName: odocontext.GetApplication(ctx),
|
||||
DevfileWatchHandler: o.regenerateAdapterAndPush,
|
||||
FileIgnores: options.IgnorePaths,
|
||||
InitialDevfileObj: devfileObj,
|
||||
InitialDevfileObj: *devfileObj,
|
||||
Debug: options.Debug,
|
||||
DevfileBuildCmd: options.BuildCommand,
|
||||
DevfileRunCmd: options.RunCommand,
|
||||
@@ -120,7 +123,7 @@ func (o *DevClient) Start(
|
||||
}
|
||||
|
||||
// RegenerateAdapterAndPush regenerates the adapter and pushes the files to remote pod
|
||||
func (o *DevClient) regenerateAdapterAndPush(pushParams adapters.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error {
|
||||
func (o *DevClient) regenerateAdapterAndPush(ctx context.Context, pushParams adapters.PushParameters, watchParams watch.WatchParameters, componentStatus *watch.ComponentStatus) error {
|
||||
var adapter component.ComponentAdapter
|
||||
|
||||
adapter, err := o.regenerateComponentAdapterFromWatchParams(watchParams)
|
||||
@@ -128,7 +131,7 @@ func (o *DevClient) regenerateAdapterAndPush(pushParams adapters.PushParameters,
|
||||
return fmt.Errorf("unable to generate component from watch parameters: %w", err)
|
||||
}
|
||||
|
||||
err = adapter.Push(pushParams, componentStatus)
|
||||
err = adapter.Push(ctx, pushParams, componentStatus)
|
||||
if err != nil {
|
||||
return fmt.Errorf("watch command was unable to push component: %w", err)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package dev
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/devfile/library/pkg/devfile/parser"
|
||||
)
|
||||
|
||||
type StartOptions struct {
|
||||
@@ -30,10 +28,6 @@ type Client interface {
|
||||
// It logs messages and errors to out and errOut.
|
||||
Start(
|
||||
ctx context.Context,
|
||||
devfileObj parser.DevfileObj,
|
||||
componentName string,
|
||||
path string,
|
||||
devfilePath string,
|
||||
out io.Writer,
|
||||
errOut io.Writer,
|
||||
options StartOptions,
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
io "io"
|
||||
reflect "reflect"
|
||||
|
||||
parser "github.com/devfile/library/pkg/devfile/parser"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
@@ -37,15 +36,15 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
||||
}
|
||||
|
||||
// Start mocks base method.
|
||||
func (m *MockClient) Start(ctx context.Context, devfileObj parser.DevfileObj, componentName, path, devfilePath string, out, errOut io.Writer, options StartOptions) error {
|
||||
func (m *MockClient) Start(ctx context.Context, out, errOut io.Writer, options StartOptions) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Start", ctx, devfileObj, componentName, path, devfilePath, out, errOut, options)
|
||||
ret := m.ctrl.Call(m, "Start", ctx, out, errOut, options)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Start indicates an expected call of Start.
|
||||
func (mr *MockClientMockRecorder) Start(ctx, devfileObj, componentName, path, devfilePath, out, errOut, options interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) Start(ctx, out, errOut, options interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockClient)(nil).Start), ctx, devfileObj, componentName, path, devfilePath, out, errOut, options)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockClient)(nil).Start), ctx, out, errOut, options)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
@@ -89,7 +90,7 @@ func NewKubernetesAdapter(
|
||||
// Push updates the component if a matching component exists or creates one if it doesn't exist
|
||||
// Once the component has started, it will sync the source code to it.
|
||||
// The componentStatus will be modified to reflect the status of the component when the function returns
|
||||
func (a Adapter) Push(parameters adapters.PushParameters, componentStatus *watch.ComponentStatus) (err error) {
|
||||
func (a Adapter) Push(ctx context.Context, parameters adapters.PushParameters, componentStatus *watch.ComponentStatus) (err error) {
|
||||
|
||||
// preliminary checks
|
||||
err = dfutil.ValidateK8sResourceName("component name", a.ComponentName)
|
||||
@@ -260,6 +261,7 @@ func (a Adapter) Push(parameters adapters.PushParameters, componentStatus *watch
|
||||
devfile: a.Devfile,
|
||||
path: parameters.Path,
|
||||
podName: pod.GetName(),
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
if commandType == devfilev1.ExecCommandType {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
"github.com/devfile/library/pkg/devfile/parser"
|
||||
"github.com/redhat-developer/odo/pkg/component"
|
||||
@@ -23,12 +25,14 @@ type runHandler struct {
|
||||
path string
|
||||
componentExists bool
|
||||
podName string
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
var _ libdevfile.Handler = (*runHandler)(nil)
|
||||
|
||||
func (a *runHandler) ApplyImage(img devfilev1.Component) error {
|
||||
return image.BuildPushSpecificImage(a.fs, a.path, img, true)
|
||||
return image.BuildPushSpecificImage(a.ctx, a.fs, img, true)
|
||||
}
|
||||
|
||||
func (a *runHandler) ApplyKubernetes(kubernetes devfilev1.Component) error {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/devfile/adapters"
|
||||
"github.com/redhat-developer/odo/pkg/watch"
|
||||
)
|
||||
|
||||
// ComponentAdapter defines the functions that platform-specific adapters must implement
|
||||
type ComponentAdapter interface {
|
||||
Push(parameters adapters.PushParameters, componentStatus *watch.ComponentStatus) error
|
||||
Push(ctx context.Context, parameters adapters.PushParameters, componentStatus *watch.ComponentStatus) error
|
||||
}
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
"github.com/devfile/library/pkg/devfile/parser"
|
||||
"github.com/devfile/library/pkg/devfile/parser/data/v2/common"
|
||||
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/libdevfile"
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
|
||||
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
|
||||
)
|
||||
|
||||
@@ -27,13 +29,17 @@ type Backend interface {
|
||||
}
|
||||
|
||||
var lookPathCmd = exec.LookPath
|
||||
var getEnvFunc = os.Getenv
|
||||
|
||||
// BuildPushImages build all images defined in the devfile with the detected backend
|
||||
// If push is true, also push the images to their registries
|
||||
func BuildPushImages(fs filesystem.Filesystem, devfileObj parser.DevfileObj, path string, push bool) error {
|
||||
func BuildPushImages(ctx context.Context, fs filesystem.Filesystem, push bool) error {
|
||||
var (
|
||||
devfileObj = odocontext.GetDevfileObj(ctx)
|
||||
devfilePath = odocontext.GetDevfilePath(ctx)
|
||||
path = filepath.Dir(devfilePath)
|
||||
)
|
||||
|
||||
backend, err := selectBackend()
|
||||
backend, err := selectBackend(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -59,13 +65,16 @@ func BuildPushImages(fs filesystem.Filesystem, devfileObj parser.DevfileObj, pat
|
||||
|
||||
// BuildPushSpecificImage build an image defined in the devfile present in devfilePath
|
||||
// If push is true, also push the image to its registry
|
||||
func BuildPushSpecificImage(fs filesystem.Filesystem, devfilePath string, component devfile.Component, push bool) error {
|
||||
backend, err := selectBackend()
|
||||
func BuildPushSpecificImage(ctx context.Context, fs filesystem.Filesystem, component devfile.Component, push bool) error {
|
||||
var (
|
||||
devfilePath = odocontext.GetDevfilePath(ctx)
|
||||
path = filepath.Dir(devfilePath)
|
||||
)
|
||||
backend, err := selectBackend(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return buildPushImage(backend, fs, component.Image, devfilePath, push)
|
||||
return buildPushImage(backend, fs, component.Image, path, push)
|
||||
}
|
||||
|
||||
// buildPushImage build an image using the provided backend
|
||||
@@ -91,12 +100,9 @@ func buildPushImage(backend Backend, fs filesystem.Filesystem, image *devfile.Im
|
||||
// selectBackend selects the container backend to use for building and pushing images
|
||||
// It will detect podman and docker CLIs (in this order),
|
||||
// or return an error if none are present locally
|
||||
func selectBackend() (Backend, error) {
|
||||
func selectBackend(ctx context.Context) (Backend, error) {
|
||||
|
||||
podmanCmd := getEnvFunc("PODMAN_CMD")
|
||||
if podmanCmd == "" {
|
||||
podmanCmd = "podman"
|
||||
}
|
||||
podmanCmd := envcontext.GetEnvConfig(ctx).PodmanCmd
|
||||
if _, err := lookPathCmd(podmanCmd); err == nil {
|
||||
|
||||
// Podman does NOT build x86 images on Apple Silicon / M1 and we must *WARN* the user that this will not work.
|
||||
@@ -116,10 +122,7 @@ func selectBackend() (Backend, error) {
|
||||
return NewDockerCompatibleBackend(podmanCmd), nil
|
||||
}
|
||||
|
||||
dockerCmd := getEnvFunc("DOCKER_CMD")
|
||||
if dockerCmd == "" {
|
||||
dockerCmd = "docker"
|
||||
}
|
||||
dockerCmd := envcontext.GetEnvConfig(ctx).DockerCmd
|
||||
if _, err := lookPathCmd(dockerCmd); err == nil {
|
||||
return NewDockerCompatibleBackend(dockerCmd), nil
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
devfile "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
|
||||
)
|
||||
|
||||
@@ -120,13 +122,17 @@ func TestBuildPushImage(t *testing.T) {
|
||||
func TestSelectBackend(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
getEnvFunc func(string) string
|
||||
envConfig config.Configuration
|
||||
lookPathCmd func(string) (string, error)
|
||||
wantType string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "all backends are present",
|
||||
envConfig: config.Configuration{
|
||||
DockerCmd: "docker",
|
||||
PodmanCmd: "podman",
|
||||
},
|
||||
lookPathCmd: func(string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
@@ -135,6 +141,10 @@ func TestSelectBackend(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "no backend are present",
|
||||
envConfig: config.Configuration{
|
||||
DockerCmd: "docker",
|
||||
PodmanCmd: "podman",
|
||||
},
|
||||
lookPathCmd: func(string) (string, error) {
|
||||
return "", errors.New("")
|
||||
},
|
||||
@@ -142,6 +152,10 @@ func TestSelectBackend(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "only docker is present",
|
||||
envConfig: config.Configuration{
|
||||
DockerCmd: "docker",
|
||||
PodmanCmd: "podman",
|
||||
},
|
||||
lookPathCmd: func(name string) (string, error) {
|
||||
if name == "docker" {
|
||||
return "docker", nil
|
||||
@@ -153,6 +167,10 @@ func TestSelectBackend(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "only podman is present",
|
||||
envConfig: config.Configuration{
|
||||
DockerCmd: "docker",
|
||||
PodmanCmd: "podman",
|
||||
},
|
||||
lookPathCmd: func(name string) (string, error) {
|
||||
if name == "podman" {
|
||||
return "podman", nil
|
||||
@@ -164,11 +182,9 @@ func TestSelectBackend(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "value of PODMAN_CMD envvar is returned if it points to a valid command",
|
||||
getEnvFunc: func(name string) string {
|
||||
if name == "PODMAN_CMD" {
|
||||
return "my-alternate-podman-command"
|
||||
}
|
||||
return ""
|
||||
envConfig: config.Configuration{
|
||||
DockerCmd: "docker",
|
||||
PodmanCmd: "my-alternate-podman-command",
|
||||
},
|
||||
lookPathCmd: func(name string) (string, error) {
|
||||
if name == "my-alternate-podman-command" {
|
||||
@@ -181,11 +197,9 @@ func TestSelectBackend(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "docker if PODMAN_CMD points to an invalid command",
|
||||
getEnvFunc: func(name string) string {
|
||||
if name == "PODMAN_CMD" {
|
||||
return "no-such-command"
|
||||
}
|
||||
return ""
|
||||
envConfig: config.Configuration{
|
||||
PodmanCmd: "no-such-command",
|
||||
DockerCmd: "docker",
|
||||
},
|
||||
lookPathCmd: func(name string) (string, error) {
|
||||
if name == "docker" {
|
||||
@@ -200,18 +214,11 @@ func TestSelectBackend(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.getEnvFunc != nil {
|
||||
getEnvFunc = tt.getEnvFunc
|
||||
} else {
|
||||
getEnvFunc = func(string) string {
|
||||
//Empty environment
|
||||
return ""
|
||||
}
|
||||
}
|
||||
defer func() { getEnvFunc = os.Getenv }()
|
||||
lookPathCmd = tt.lookPathCmd
|
||||
defer func() { lookPathCmd = exec.LookPath }()
|
||||
backend, err := selectBackend()
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, tt.envConfig)
|
||||
backend, err := selectBackend(ctx)
|
||||
if tt.wantErr != (err != nil) {
|
||||
t.Errorf("%s: Error result wanted %v, got %v", tt.name, tt.wantErr, err != nil)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
@@ -30,8 +31,8 @@ func (o *AlizerBackend) Validate(flags map[string]string, fs filesystem.Filesyst
|
||||
}
|
||||
|
||||
// SelectDevfile calls thz Alizer to detect the devfile and asks for confirmation to the user
|
||||
func (o *AlizerBackend) SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (location *api.DevfileLocation, err error) {
|
||||
selected, registry, err := o.alizerClient.DetectFramework(dir)
|
||||
func (o *AlizerBackend) SelectDevfile(ctx context.Context, flags map[string]string, fs filesystem.Filesystem, dir string) (location *api.DevfileLocation, err error) {
|
||||
selected, registry, err := o.alizerClient.DetectFramework(ctx, dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -49,7 +50,7 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
|
||||
},
|
||||
alizerClient: func(ctrl *gomock.Controller) alizer.Client {
|
||||
alizerClient := alizer.NewMockClient(ctrl)
|
||||
alizerClient.EXPECT().DetectFramework(gomock.Any()).Return(recognizer.DevFileType{
|
||||
alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(recognizer.DevFileType{
|
||||
Name: "a-devfile-name",
|
||||
}, api.Registry{
|
||||
Name: "a-registry",
|
||||
@@ -76,7 +77,7 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
|
||||
},
|
||||
alizerClient: func(ctrl *gomock.Controller) alizer.Client {
|
||||
alizerClient := alizer.NewMockClient(ctrl)
|
||||
alizerClient.EXPECT().DetectFramework(gomock.Any()).Return(recognizer.DevFileType{}, api.Registry{}, nil)
|
||||
alizerClient.EXPECT().DetectFramework(gomock.Any(), gomock.Any()).Return(recognizer.DevFileType{}, api.Registry{}, nil)
|
||||
return alizerClient
|
||||
},
|
||||
},
|
||||
@@ -95,7 +96,8 @@ func TestAlizerBackend_SelectDevfile(t *testing.T) {
|
||||
askerClient: tt.fields.askerClient(ctrl),
|
||||
alizerClient: tt.fields.alizerClient(ctrl),
|
||||
}
|
||||
gotLocation, err := o.SelectDevfile(tt.args.flags, tt.args.fs, tt.args.dir)
|
||||
ctx := context.Background()
|
||||
gotLocation, err := o.SelectDevfile(ctx, tt.args.flags, tt.args.fs, tt.args.dir)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("AlizerBackend.SelectDevfile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
@@ -85,7 +86,7 @@ func (o *FlagsBackend) Validate(flags map[string]string, fs filesystem.Filesyste
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *FlagsBackend) SelectDevfile(flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DevfileLocation, error) {
|
||||
func (o *FlagsBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DevfileLocation, error) {
|
||||
return &api.DevfileLocation{
|
||||
Devfile: flags[FLAG_DEVFILE],
|
||||
DevfileRegistry: flags[FLAG_DEVFILE_REGISTRY],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -47,7 +48,8 @@ func TestFlagsBackend_SelectDevfile(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &FlagsBackend{}
|
||||
got, err := o.SelectDevfile(tt.fields.flags, nil, "")
|
||||
ctx := context.Background()
|
||||
got, err := o.SelectDevfile(ctx, tt.fields.flags, nil, "")
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("FlagsBackend.SelectDevfile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -47,9 +48,9 @@ func (o *InteractiveBackend) Validate(flags map[string]string, fs filesystem.Fil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *InteractiveBackend) SelectDevfile(flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DevfileLocation, error) {
|
||||
func (o *InteractiveBackend) SelectDevfile(ctx context.Context, flags map[string]string, _ filesystem.Filesystem, _ string) (*api.DevfileLocation, error) {
|
||||
result := &api.DevfileLocation{}
|
||||
devfileEntries, _ := o.registryClient.ListDevfileStacks("", "", "", false)
|
||||
devfileEntries, _ := o.registryClient.ListDevfileStacks(ctx, "", "", "", false)
|
||||
|
||||
langs := devfileEntries.GetLanguages()
|
||||
state := STATE_ASK_LANG
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -46,7 +47,7 @@ func TestInteractiveBackend_SelectDevfile(t *testing.T) {
|
||||
},
|
||||
buildCatalogClient: func(ctrl *gomock.Controller) registry.Client {
|
||||
client := registry.NewMockClient(ctrl)
|
||||
client.EXPECT().ListDevfileStacks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
|
||||
client.EXPECT().ListDevfileStacks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
|
||||
return client
|
||||
},
|
||||
},
|
||||
@@ -73,7 +74,7 @@ func TestInteractiveBackend_SelectDevfile(t *testing.T) {
|
||||
},
|
||||
buildCatalogClient: func(ctrl *gomock.Controller) registry.Client {
|
||||
client := registry.NewMockClient(ctrl)
|
||||
client.EXPECT().ListDevfileStacks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
|
||||
client.EXPECT().ListDevfileStacks(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
|
||||
return client
|
||||
},
|
||||
},
|
||||
@@ -90,7 +91,8 @@ func TestInteractiveBackend_SelectDevfile(t *testing.T) {
|
||||
askerClient: tt.fields.buildAsker(ctrl),
|
||||
registryClient: tt.fields.buildCatalogClient(ctrl),
|
||||
}
|
||||
got, err := o.SelectDevfile(map[string]string{}, nil, "")
|
||||
ctx := context.Background()
|
||||
got, err := o.SelectDevfile(ctx, map[string]string{}, nil, "")
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("InteractiveBuilder.ParamsBuild() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
"github.com/devfile/library/pkg/devfile/parser"
|
||||
"github.com/redhat-developer/odo/pkg/api"
|
||||
@@ -16,7 +18,7 @@ type InitBackend interface {
|
||||
Validate(flags map[string]string, fs filesystem.Filesystem, dir string) error
|
||||
|
||||
// SelectDevfile selects a devfile and returns its location information, depending on the flags
|
||||
SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (location *api.DevfileLocation, err error)
|
||||
SelectDevfile(ctx context.Context, flags map[string]string, fs filesystem.Filesystem, dir string) (location *api.DevfileLocation, err error)
|
||||
|
||||
// SelectStarterProject selects a starter project from the devfile and returns information about the starter project,
|
||||
// depending on the flags. If not starter project is selected, a nil starter is returned
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
v1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
@@ -68,18 +69,18 @@ func (mr *MockInitBackendMockRecorder) PersonalizeName(devfile, flags interface{
|
||||
}
|
||||
|
||||
// SelectDevfile mocks base method.
|
||||
func (m *MockInitBackend) SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DevfileLocation, error) {
|
||||
func (m *MockInitBackend) SelectDevfile(ctx context.Context, flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DevfileLocation, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SelectDevfile", flags, fs, dir)
|
||||
ret := m.ctrl.Call(m, "SelectDevfile", ctx, flags, fs, dir)
|
||||
ret0, _ := ret[0].(*api.DevfileLocation)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SelectDevfile indicates an expected call of SelectDevfile.
|
||||
func (mr *MockInitBackendMockRecorder) SelectDevfile(flags, fs, dir interface{}) *gomock.Call {
|
||||
func (mr *MockInitBackendMockRecorder) SelectDevfile(ctx, flags, fs, dir interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockInitBackend)(nil).SelectDevfile), flags, fs, dir)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockInitBackend)(nil).SelectDevfile), ctx, flags, fs, dir)
|
||||
}
|
||||
|
||||
// SelectStarterProject mocks base method.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
@@ -76,7 +77,7 @@ func (o *InitClient) Validate(flags map[string]string, fs filesystem.Filesystem,
|
||||
}
|
||||
|
||||
// SelectDevfile calls SelectDevfile methods of the adequate backend
|
||||
func (o *InitClient) SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DevfileLocation, error) {
|
||||
func (o *InitClient) SelectDevfile(ctx context.Context, flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DevfileLocation, error) {
|
||||
var backend backend.InitBackend
|
||||
|
||||
empty, err := location.DirIsEmpty(fs, dir)
|
||||
@@ -90,7 +91,7 @@ func (o *InitClient) SelectDevfile(flags map[string]string, fs filesystem.Filesy
|
||||
} else {
|
||||
backend = o.flagsBackend
|
||||
}
|
||||
location, err := backend.SelectDevfile(flags, fs, dir)
|
||||
location, err := backend.SelectDevfile(ctx, flags, fs, dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -99,7 +100,7 @@ func (o *InitClient) SelectDevfile(flags map[string]string, fs filesystem.Filesy
|
||||
if location == nil {
|
||||
if backend == o.alizerBackend {
|
||||
backend = o.interactiveBackend
|
||||
return backend.SelectDevfile(flags, fs, dir)
|
||||
return backend.SelectDevfile(ctx, flags, fs, dir)
|
||||
} else {
|
||||
return nil, errors.New("unable to determine the devfile location")
|
||||
}
|
||||
@@ -108,12 +109,12 @@ func (o *InitClient) SelectDevfile(flags map[string]string, fs filesystem.Filesy
|
||||
return location, err
|
||||
}
|
||||
|
||||
func (o *InitClient) DownloadDevfile(devfileLocation *api.DevfileLocation, destDir string) (string, error) {
|
||||
func (o *InitClient) DownloadDevfile(ctx context.Context, devfileLocation *api.DevfileLocation, destDir string) (string, error) {
|
||||
destDevfile := filepath.Join(destDir, "devfile.yaml")
|
||||
if devfileLocation.DevfilePath != "" {
|
||||
return destDevfile, o.downloadDirect(devfileLocation.DevfilePath, destDevfile)
|
||||
} else {
|
||||
return destDevfile, o.downloadFromRegistry(devfileLocation.DevfileRegistry, devfileLocation.Devfile, destDir)
|
||||
return destDevfile, o.downloadFromRegistry(ctx, devfileLocation.DevfileRegistry, devfileLocation.Devfile, destDir)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +162,7 @@ func (o *InitClient) downloadDirect(URL string, dest string) error {
|
||||
|
||||
// downloadFromRegistry downloads a devfile from the provided registry and saves it in dest
|
||||
// If registryName is empty, will try to download the devfile from the list of registries in preferences
|
||||
func (o *InitClient) downloadFromRegistry(registryName string, devfile string, dest string) error {
|
||||
func (o *InitClient) downloadFromRegistry(ctx context.Context, registryName string, devfile string, dest string) error {
|
||||
var downloadSpinner *log.Status
|
||||
var forceRegistry bool
|
||||
if registryName == "" {
|
||||
@@ -177,14 +178,14 @@ func (o *InitClient) downloadFromRegistry(registryName string, devfile string, d
|
||||
var reg preference.Registry
|
||||
for _, reg = range registries {
|
||||
if forceRegistry && reg.Name == registryName {
|
||||
err := o.registryClient.PullStackFromRegistry(reg.URL, devfile, dest, segment.GetRegistryOptions())
|
||||
err := o.registryClient.PullStackFromRegistry(reg.URL, devfile, dest, segment.GetRegistryOptions(ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
downloadSpinner.End(true)
|
||||
return nil
|
||||
} else if !forceRegistry {
|
||||
err := o.registryClient.PullStackFromRegistry(reg.URL, devfile, dest, segment.GetRegistryOptions())
|
||||
err := o.registryClient.PullStackFromRegistry(reg.URL, devfile, dest, segment.GetRegistryOptions(ctx))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -254,13 +255,13 @@ func (o InitClient) PersonalizeDevfileConfig(devfileobj parser.DevfileObj, flags
|
||||
return backend.PersonalizeDevfileConfig(devfileobj)
|
||||
}
|
||||
|
||||
func (o InitClient) SelectAndPersonalizeDevfile(flags map[string]string, contextDir string) (parser.DevfileObj, string, *api.DevfileLocation, error) {
|
||||
devfileLocation, err := o.SelectDevfile(flags, o.fsys, contextDir)
|
||||
func (o InitClient) SelectAndPersonalizeDevfile(ctx context.Context, flags map[string]string, contextDir string) (parser.DevfileObj, string, *api.DevfileLocation, error) {
|
||||
devfileLocation, err := o.SelectDevfile(ctx, flags, o.fsys, contextDir)
|
||||
if err != nil {
|
||||
return parser.DevfileObj{}, "", nil, err
|
||||
}
|
||||
|
||||
devfilePath, err := o.DownloadDevfile(devfileLocation, contextDir)
|
||||
devfilePath, err := o.DownloadDevfile(ctx, devfileLocation, contextDir)
|
||||
if err != nil {
|
||||
return parser.DevfileObj{}, "", nil, fmt.Errorf("unable to download devfile: %w", err)
|
||||
}
|
||||
@@ -277,7 +278,7 @@ func (o InitClient) SelectAndPersonalizeDevfile(flags map[string]string, context
|
||||
return devfileObj, devfilePath, devfileLocation, nil
|
||||
}
|
||||
|
||||
func (o InitClient) InitDevfile(flags map[string]string, contextDir string,
|
||||
func (o InitClient) InitDevfile(ctx context.Context, flags map[string]string, contextDir string,
|
||||
preInitHandlerFunc func(interactiveMode bool), newDevfileHandlerFunc func(newDevfileObj parser.DevfileObj) error) error {
|
||||
|
||||
containsDevfile, err := location.DirectoryContainsDevfile(o.fsys, contextDir)
|
||||
@@ -292,7 +293,7 @@ func (o InitClient) InitDevfile(flags map[string]string, contextDir string,
|
||||
preInitHandlerFunc(len(flags) == 0)
|
||||
}
|
||||
|
||||
devfileObj, _, _, err := o.SelectAndPersonalizeDevfile(map[string]string{}, contextDir)
|
||||
devfileObj, _, _, err := o.SelectAndPersonalizeDevfile(ctx, map[string]string{}, contextDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
@@ -8,6 +9,8 @@ import (
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/api"
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
"github.com/redhat-developer/odo/pkg/registry"
|
||||
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
|
||||
@@ -163,7 +166,9 @@ func TestInitClient_downloadFromRegistry(t *testing.T) {
|
||||
preferenceClient: tt.fields.preferenceClient(ctrl),
|
||||
registryClient: tt.fields.registryClient(ctrl),
|
||||
}
|
||||
if err := o.downloadFromRegistry(tt.args.registryName, tt.args.devfile, tt.args.dest); (err != nil) != tt.wantErr {
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{})
|
||||
if err := o.downloadFromRegistry(ctx, tt.args.registryName, tt.args.devfile, tt.args.dest); (err != nil) != tt.wantErr {
|
||||
t.Errorf("InitClient.downloadFromRegistry() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
"github.com/devfile/library/pkg/devfile/parser"
|
||||
|
||||
@@ -29,17 +31,17 @@ type Client interface {
|
||||
// `newDevfileHandlerFunc` is called only when a new Devfile object has been instantiated.
|
||||
// It allows to perform operations right after the Devfile has been initialized and personalized.
|
||||
// It is not called if the context directory already has a Devfile file.
|
||||
InitDevfile(flags map[string]string, contextDir string, preInitHandlerFunc func(interactiveMode bool),
|
||||
InitDevfile(ctx context.Context, flags map[string]string, contextDir string, preInitHandlerFunc func(interactiveMode bool),
|
||||
newDevfileHandlerFunc func(newDevfileObj parser.DevfileObj) error) error
|
||||
|
||||
// SelectDevfile returns information about a devfile selected based on Alizer if the directory content,
|
||||
// or based on the flags if the directory is empty, or
|
||||
// interactively if flags is empty
|
||||
SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DevfileLocation, error)
|
||||
SelectDevfile(ctx context.Context, flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DevfileLocation, error)
|
||||
|
||||
// DownloadDevfile downloads a devfile given its location information and a destination directory
|
||||
// and returns the path of the downloaded file
|
||||
DownloadDevfile(devfileLocation *api.DevfileLocation, destDir string) (string, error)
|
||||
DownloadDevfile(ctx context.Context, devfileLocation *api.DevfileLocation, destDir string) (string, error)
|
||||
|
||||
// SelectStarterProject selects a starter project from the devfile and returns information about the starter project,
|
||||
// depending on the flags. If not starter project is selected, a nil starter is returned
|
||||
@@ -58,5 +60,5 @@ type Client interface {
|
||||
|
||||
// SelectAndPersonalizeDevfile selects a devfile, then downloads, parse and personalize it
|
||||
// Returns the devfile object, its path and pointer to *api.devfileLocation
|
||||
SelectAndPersonalizeDevfile(flags map[string]string, contextDir string) (parser.DevfileObj, string, *api.DevfileLocation, error)
|
||||
SelectAndPersonalizeDevfile(ctx context.Context, flags map[string]string, contextDir string) (parser.DevfileObj, string, *api.DevfileLocation, error)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
v1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
@@ -38,18 +39,18 @@ func (m *MockClient) EXPECT() *MockClientMockRecorder {
|
||||
}
|
||||
|
||||
// DownloadDevfile mocks base method.
|
||||
func (m *MockClient) DownloadDevfile(devfileLocation *api.DevfileLocation, destDir string) (string, error) {
|
||||
func (m *MockClient) DownloadDevfile(ctx context.Context, devfileLocation *api.DevfileLocation, destDir string) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DownloadDevfile", devfileLocation, destDir)
|
||||
ret := m.ctrl.Call(m, "DownloadDevfile", ctx, devfileLocation, destDir)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DownloadDevfile indicates an expected call of DownloadDevfile.
|
||||
func (mr *MockClientMockRecorder) DownloadDevfile(devfileLocation, destDir interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) DownloadDevfile(ctx, devfileLocation, destDir interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadDevfile", reflect.TypeOf((*MockClient)(nil).DownloadDevfile), devfileLocation, destDir)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadDevfile", reflect.TypeOf((*MockClient)(nil).DownloadDevfile), ctx, devfileLocation, destDir)
|
||||
}
|
||||
|
||||
// DownloadStarterProject mocks base method.
|
||||
@@ -81,17 +82,17 @@ func (mr *MockClientMockRecorder) GetFlags(flags interface{}) *gomock.Call {
|
||||
}
|
||||
|
||||
// InitDevfile mocks base method.
|
||||
func (m *MockClient) InitDevfile(flags map[string]string, contextDir string, preInitHandlerFunc func(bool), newDevfileHandlerFunc func(parser.DevfileObj) error) error {
|
||||
func (m *MockClient) InitDevfile(ctx context.Context, flags map[string]string, contextDir string, preInitHandlerFunc func(bool), newDevfileHandlerFunc func(parser.DevfileObj) error) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InitDevfile", flags, contextDir, preInitHandlerFunc, newDevfileHandlerFunc)
|
||||
ret := m.ctrl.Call(m, "InitDevfile", ctx, flags, contextDir, preInitHandlerFunc, newDevfileHandlerFunc)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// InitDevfile indicates an expected call of InitDevfile.
|
||||
func (mr *MockClientMockRecorder) InitDevfile(flags, contextDir, preInitHandlerFunc, newDevfileHandlerFunc interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) InitDevfile(ctx, flags, contextDir, preInitHandlerFunc, newDevfileHandlerFunc interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitDevfile", reflect.TypeOf((*MockClient)(nil).InitDevfile), flags, contextDir, preInitHandlerFunc, newDevfileHandlerFunc)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitDevfile", reflect.TypeOf((*MockClient)(nil).InitDevfile), ctx, flags, contextDir, preInitHandlerFunc, newDevfileHandlerFunc)
|
||||
}
|
||||
|
||||
// PersonalizeDevfileConfig mocks base method.
|
||||
@@ -125,9 +126,9 @@ func (mr *MockClientMockRecorder) PersonalizeName(devfile, flags interface{}) *g
|
||||
}
|
||||
|
||||
// SelectAndPersonalizeDevfile mocks base method.
|
||||
func (m *MockClient) SelectAndPersonalizeDevfile(flags map[string]string, contextDir string) (parser.DevfileObj, string, *api.DevfileLocation, error) {
|
||||
func (m *MockClient) SelectAndPersonalizeDevfile(ctx context.Context, flags map[string]string, contextDir string) (parser.DevfileObj, string, *api.DevfileLocation, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SelectAndPersonalizeDevfile", flags, contextDir)
|
||||
ret := m.ctrl.Call(m, "SelectAndPersonalizeDevfile", ctx, flags, contextDir)
|
||||
ret0, _ := ret[0].(parser.DevfileObj)
|
||||
ret1, _ := ret[1].(string)
|
||||
ret2, _ := ret[2].(*api.DevfileLocation)
|
||||
@@ -136,24 +137,24 @@ func (m *MockClient) SelectAndPersonalizeDevfile(flags map[string]string, contex
|
||||
}
|
||||
|
||||
// SelectAndPersonalizeDevfile indicates an expected call of SelectAndPersonalizeDevfile.
|
||||
func (mr *MockClientMockRecorder) SelectAndPersonalizeDevfile(flags, contextDir interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SelectAndPersonalizeDevfile(ctx, flags, contextDir interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectAndPersonalizeDevfile", reflect.TypeOf((*MockClient)(nil).SelectAndPersonalizeDevfile), flags, contextDir)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectAndPersonalizeDevfile", reflect.TypeOf((*MockClient)(nil).SelectAndPersonalizeDevfile), ctx, flags, contextDir)
|
||||
}
|
||||
|
||||
// SelectDevfile mocks base method.
|
||||
func (m *MockClient) SelectDevfile(flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DevfileLocation, error) {
|
||||
func (m *MockClient) SelectDevfile(ctx context.Context, flags map[string]string, fs filesystem.Filesystem, dir string) (*api.DevfileLocation, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SelectDevfile", flags, fs, dir)
|
||||
ret := m.ctrl.Call(m, "SelectDevfile", ctx, flags, fs, dir)
|
||||
ret0, _ := ret[0].(*api.DevfileLocation)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SelectDevfile indicates an expected call of SelectDevfile.
|
||||
func (mr *MockClientMockRecorder) SelectDevfile(flags, fs, dir interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) SelectDevfile(ctx, flags, fs, dir interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockClient)(nil).SelectDevfile), flags, fs, dir)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SelectDevfile", reflect.TypeOf((*MockClient)(nil).SelectDevfile), ctx, flags, fs, dir)
|
||||
}
|
||||
|
||||
// SelectStarterProject mocks base method.
|
||||
|
||||
@@ -49,7 +49,7 @@ func (o *AlizerOptions) Run(ctx context.Context) (err error) {
|
||||
// Run contains the logic for the odo command
|
||||
func (o *AlizerOptions) RunForJsonOutput(ctx context.Context) (out interface{}, err error) {
|
||||
workingDir := odocontext.GetWorkingDirectory(ctx)
|
||||
df, reg, err := o.clientset.AlizerClient.DetectFramework(workingDir)
|
||||
df, reg, err := o.clientset.AlizerClient.DetectFramework(ctx, workingDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package build_images
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
@@ -63,12 +62,7 @@ func (o *BuildImagesOptions) Validate(ctx context.Context) (err error) {
|
||||
|
||||
// Run contains the logic for the odo command
|
||||
func (o *BuildImagesOptions) Run(ctx context.Context) (err error) {
|
||||
var (
|
||||
devfileObj = odocontext.GetDevfileObj(ctx)
|
||||
devfilePath = odocontext.GetDevfilePath(ctx)
|
||||
path = filepath.Dir(devfilePath)
|
||||
)
|
||||
return image.BuildPushImages(o.clientset.FS, *devfileObj, path, o.pushFlag)
|
||||
return image.BuildPushImages(ctx, o.clientset.FS, o.pushFlag)
|
||||
}
|
||||
|
||||
// NewCmdBuildImages implements the odo command
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -101,8 +102,8 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e
|
||||
const pluginPrefix = "odo"
|
||||
|
||||
// NewCmdOdo creates a new root command for odo
|
||||
func NewCmdOdo(name, fullName string) *cobra.Command {
|
||||
rootCmd := odoRootCmd(name, fullName)
|
||||
func NewCmdOdo(ctx context.Context, name, fullName string) *cobra.Command {
|
||||
rootCmd := odoRootCmd(ctx, name, fullName)
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
cmdPathPieces := os.Args[1:]
|
||||
@@ -120,7 +121,7 @@ func NewCmdOdo(name, fullName string) *cobra.Command {
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
func odoRootCmd(name, fullName string) *cobra.Command {
|
||||
func odoRootCmd(ctx context.Context, name, fullName string) *cobra.Command {
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
rootCmd := &cobra.Command{
|
||||
Use: name,
|
||||
@@ -135,7 +136,7 @@ func odoRootCmd(name, fullName string) *cobra.Command {
|
||||
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.odo.yaml)")
|
||||
|
||||
commonflags.AddOutputFlag()
|
||||
commonflags.AddRunOnFlag()
|
||||
commonflags.AddRunOnFlag(ctx)
|
||||
commonflags.AddVariablesFlags()
|
||||
|
||||
// Here we add the necessary "logging" flags.. However, we choose to hide some of these from the user
|
||||
@@ -177,7 +178,7 @@ func odoRootCmd(name, fullName string) *cobra.Command {
|
||||
login.NewCmdLogin(login.RecommendedCommandName, util.GetFullName(fullName, login.RecommendedCommandName)),
|
||||
logout.NewCmdLogout(logout.RecommendedCommandName, util.GetFullName(fullName, logout.RecommendedCommandName)),
|
||||
version.NewCmdVersion(version.RecommendedCommandName, util.GetFullName(fullName, version.RecommendedCommandName)),
|
||||
preference.NewCmdPreference(preference.RecommendedCommandName, util.GetFullName(fullName, preference.RecommendedCommandName)),
|
||||
preference.NewCmdPreference(ctx, preference.RecommendedCommandName, util.GetFullName(fullName, preference.RecommendedCommandName)),
|
||||
telemetry.NewCmdTelemetry(telemetry.RecommendedCommandName),
|
||||
list.NewCmdList(list.RecommendedCommandName, util.GetFullName(fullName, list.RecommendedCommandName)),
|
||||
build_images.NewCmdBuildImages(build_images.RecommendedCommandName, util.GetFullName(fullName, build_images.RecommendedCommandName)),
|
||||
|
||||
@@ -3,7 +3,6 @@ package deploy
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/component"
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
@@ -68,10 +67,7 @@ func (o *DeployOptions) Validate(ctx context.Context) error {
|
||||
func (o *DeployOptions) Run(ctx context.Context) error {
|
||||
var (
|
||||
devfileObj = odocontext.GetDevfileObj(ctx)
|
||||
devfilePath = odocontext.GetDevfilePath(ctx)
|
||||
path = filepath.Dir(devfilePath)
|
||||
devfileName = odocontext.GetComponentName(ctx)
|
||||
appName = odocontext.GetApplication(ctx)
|
||||
namespace = odocontext.GetNamespace(ctx)
|
||||
)
|
||||
|
||||
@@ -85,7 +81,7 @@ func (o *DeployOptions) Run(ctx context.Context) error {
|
||||
"odo version: "+version.VERSION)
|
||||
|
||||
// Run actual deploy command to be used
|
||||
err := o.clientset.DeployClient.Deploy(o.clientset.FS, *devfileObj, path, appName, devfileName)
|
||||
err := o.clientset.DeployClient.Deploy(ctx)
|
||||
|
||||
if err == nil {
|
||||
log.Info("\nYour Devfile has been successfully deployed")
|
||||
|
||||
@@ -143,10 +143,6 @@ func (o *DevOptions) Run(ctx context.Context) (err error) {
|
||||
log.Section("Deploying to the cluster in developer mode")
|
||||
return o.clientset.DevClient.Start(
|
||||
o.ctx,
|
||||
*devFileObj,
|
||||
componentName,
|
||||
path,
|
||||
devfilePath,
|
||||
o.out,
|
||||
o.errOut,
|
||||
dev.StartOptions{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package feature
|
||||
|
||||
import (
|
||||
"os"
|
||||
"context"
|
||||
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -9,6 +11,6 @@ const (
|
||||
OdoExperimentalModeTrue = "true"
|
||||
)
|
||||
|
||||
func isExperimentalModeEnabled() bool {
|
||||
return os.Getenv(OdoExperimentalModeEnvVar) == OdoExperimentalModeTrue
|
||||
func isExperimentalModeEnabled(ctx context.Context) bool {
|
||||
return envcontext.GetEnvConfig(ctx).OdoExperimentalMode
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package feature
|
||||
|
||||
import "github.com/redhat-developer/odo/pkg/log"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
)
|
||||
|
||||
// OdoFeature represents a uniquely identifiable feature of odo.
|
||||
// It can either be a CLI command or flag.
|
||||
@@ -29,14 +33,14 @@ var (
|
||||
// IsEnabled returns whether the specified feature should be enabled or not.
|
||||
// If the feature is not marked as experimental, it should always be enabled.
|
||||
// Otherwise, it is enabled only if the experimental mode is enabled (see the isExperimentalModeEnabled package-level function).
|
||||
func IsEnabled(feat OdoFeature) bool {
|
||||
func IsEnabled(ctx context.Context, feat OdoFeature) bool {
|
||||
// Features not marked as experimental are always enabled, regardless of the experimental mode
|
||||
if !feat.isExperimental {
|
||||
return true
|
||||
}
|
||||
|
||||
// Features marked as experimental are enabled only if the experimental mode is set
|
||||
experimentalModeEnabled := isExperimentalModeEnabled()
|
||||
experimentalModeEnabled := isExperimentalModeEnabled(ctx)
|
||||
if experimentalModeEnabled {
|
||||
log.Experimentalf("Experimental mode enabled for %s. Use at your own risk. More details on https://odo.dev/docs/user-guides/advanced/experimental-mode",
|
||||
feat.description)
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
package feature
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
)
|
||||
|
||||
func TestIsEnabled(t *testing.T) {
|
||||
type args struct {
|
||||
feature OdoFeature
|
||||
}
|
||||
type env struct {
|
||||
hasExperimentalModeEnvVar bool
|
||||
experimentalMode string
|
||||
experimentalMode bool
|
||||
}
|
||||
type testCase struct {
|
||||
name string
|
||||
@@ -37,8 +42,7 @@ func TestIsEnabled(t *testing.T) {
|
||||
name: "non-experimental feature should always be enabled regardless of experimental mode",
|
||||
args: args{feature: nonExperimentalFeature},
|
||||
env: env{
|
||||
hasExperimentalModeEnvVar: true,
|
||||
experimentalMode: OdoExperimentalModeTrue,
|
||||
experimentalMode: true,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
@@ -46,8 +50,7 @@ func TestIsEnabled(t *testing.T) {
|
||||
name: "non-experimental feature should always be enabled even if experimental mode is not enabled",
|
||||
args: args{feature: nonExperimentalFeature},
|
||||
env: env{
|
||||
hasExperimentalModeEnvVar: true,
|
||||
experimentalMode: "false",
|
||||
experimentalMode: false,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
@@ -60,17 +63,7 @@ func TestIsEnabled(t *testing.T) {
|
||||
name: "experimental feature should be disabled if experimental mode has an unknown value",
|
||||
args: args{feature: experimentalFeature},
|
||||
env: env{
|
||||
hasExperimentalModeEnvVar: true,
|
||||
experimentalMode: "false",
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "experimental feature should be disabled if experimental mode has an unknown value",
|
||||
args: args{feature: experimentalFeature},
|
||||
env: env{
|
||||
hasExperimentalModeEnvVar: true,
|
||||
experimentalMode: "foobar",
|
||||
experimentalMode: false,
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
@@ -78,18 +71,18 @@ func TestIsEnabled(t *testing.T) {
|
||||
name: "experimental feature should be enabled only if experimental mode is enabled",
|
||||
args: args{feature: experimentalFeature},
|
||||
env: env{
|
||||
hasExperimentalModeEnvVar: true,
|
||||
experimentalMode: OdoExperimentalModeTrue,
|
||||
experimentalMode: true,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.env.hasExperimentalModeEnvVar {
|
||||
t.Setenv(OdoExperimentalModeEnvVar, tt.env.experimentalMode)
|
||||
}
|
||||
ctx := context.Background()
|
||||
cfg := config.Configuration{}
|
||||
cfg.OdoExperimentalMode = tt.env.experimentalMode
|
||||
ctx = envcontext.WithEnvConfig(ctx, cfg)
|
||||
|
||||
got := IsEnabled(tt.args.feature)
|
||||
got := IsEnabled(ctx, tt.args.feature)
|
||||
|
||||
if got != tt.want {
|
||||
t.Errorf("IsEnabled: expected %v, but got %v. Env: %v", tt.want, got, tt.env)
|
||||
|
||||
@@ -192,7 +192,7 @@ func (o *InitOptions) run(ctx context.Context) (devfileObj parser.DevfileObj, pa
|
||||
log.Info(messages.InteractiveModeEnabled)
|
||||
}
|
||||
|
||||
devfileObj, devfilePath, devfileLocation, err := o.clientset.InitClient.SelectAndPersonalizeDevfile(o.flags, workingDir)
|
||||
devfileObj, devfilePath, devfileLocation, err := o.clientset.InitClient.SelectAndPersonalizeDevfile(ctx, o.flags, workingDir)
|
||||
if err != nil {
|
||||
return parser.DevfileObj{}, "", "", nil, nil, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package preference
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/preference/add"
|
||||
@@ -20,11 +21,11 @@ var preferenceLongDesc = ktemplates.LongDesc(`Modifies odo specific configuratio
|
||||
%[1]s`)
|
||||
|
||||
// NewCmdPreference implements the utils config odo command
|
||||
func NewCmdPreference(name, fullName string) *cobra.Command {
|
||||
func NewCmdPreference(ctx context.Context, name, fullName string) *cobra.Command {
|
||||
|
||||
// Main Commands
|
||||
preferenceViewCmd := NewCmdView(viewCommandName, util.GetFullName(fullName, viewCommandName))
|
||||
preferenceSetCmd := NewCmdSet(setCommandName, util.GetFullName(fullName, setCommandName))
|
||||
preferenceSetCmd := NewCmdSet(ctx, setCommandName, util.GetFullName(fullName, setCommandName))
|
||||
preferenceUnsetCmd := NewCmdUnset(unsetCommandName, util.GetFullName(fullName, unsetCommandName))
|
||||
preferenceAddCmd := add.NewCmdAdd(add.RecommendedCommandName, util.GetFullName(fullName, add.RecommendedCommandName))
|
||||
preferenceRemoveCmd := remove.NewCmdRemove(remove.RecommendedCommandName, util.GetFullName(fullName, remove.RecommendedCommandName))
|
||||
|
||||
@@ -87,14 +87,14 @@ func (o *SetOptions) Run(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
// NewCmdSet implements the config set odo command
|
||||
func NewCmdSet(name, fullName string) *cobra.Command {
|
||||
func NewCmdSet(ctx context.Context, name, fullName string) *cobra.Command {
|
||||
o := NewSetOptions()
|
||||
preferenceSetCmd := &cobra.Command{
|
||||
Use: name,
|
||||
Short: "Set a value in the odo preference file",
|
||||
Long: fmt.Sprintf(setLongDesc, preference.FormatSupportedParameters()),
|
||||
Example: func(exampleString, fullName string) string {
|
||||
prefClient, err := preference.NewClient()
|
||||
prefClient, err := preference.NewClient(ctx)
|
||||
if err != nil {
|
||||
util.LogErrorAndExit(err, "unable to set preference, something is wrong with odo, kindly raise an issue at https://github.com/redhat-developer/odo/issues/new?template=Bug.md")
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func (o *ListOptions) SetClientset(clientset *clientset.Clientset) {
|
||||
// Complete completes ListOptions after they've been created
|
||||
func (o *ListOptions) Complete(ctx context.Context, cmdline cmdline.Cmdline, args []string) (err error) {
|
||||
|
||||
o.devfileList, err = o.clientset.RegistryClient.ListDevfileStacks(o.registryFlag, o.devfileFlag, o.filterFlag, o.detailsFlag)
|
||||
o.devfileList, err = o.clientset.RegistryClient.ListDevfileStacks(ctx, o.registryFlag, o.devfileFlag, o.filterFlag, o.detailsFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,13 +4,18 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/odo/cmdline"
|
||||
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
|
||||
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
|
||||
"github.com/redhat-developer/odo/pkg/segment"
|
||||
scontext "github.com/redhat-developer/odo/pkg/segment/context"
|
||||
"github.com/redhat-developer/odo/pkg/util"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/klog"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
const RecommendedCommandName = "telemetry"
|
||||
@@ -42,23 +47,23 @@ func (o *TelemetryOptions) Validate(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
func (o *TelemetryOptions) Run(ctx context.Context) (err error) {
|
||||
if !segment.IsTelemetryEnabled(o.clientset.PreferenceClient) {
|
||||
if !scontext.GetTelemetryStatus(ctx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
dt := segment.GetDebugTelemetryFile()
|
||||
dt := pointer.StringDeref(envcontext.GetEnvConfig(ctx).OdoDebugTelemetryFile, "")
|
||||
if len(dt) > 0 {
|
||||
klog.V(4).Infof("WARNING: telemetry debug enabled, data logged to file %s", dt)
|
||||
return util.WriteToJSONFile(o.telemetryData, dt)
|
||||
}
|
||||
|
||||
segmentClient, err := segment.NewClient(o.clientset.PreferenceClient)
|
||||
segmentClient, err := segment.NewClient()
|
||||
if err != nil {
|
||||
klog.V(4).Infof("Cannot create a segment client. Will not send any data: %q", err)
|
||||
}
|
||||
defer segmentClient.Close()
|
||||
|
||||
err = segmentClient.Upload(o.telemetryData)
|
||||
err = segmentClient.Upload(ctx, o.telemetryData)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("Cannot send data to telemetry: %q", err)
|
||||
}
|
||||
@@ -84,6 +89,6 @@ func NewCmdTelemetry(name string) *cobra.Command {
|
||||
genericclioptions.GenericRun(o, cmd, args)
|
||||
},
|
||||
}
|
||||
clientset.Add(telemetryCmd, clientset.PREFERENCE)
|
||||
clientset.Add(telemetryCmd)
|
||||
return telemetryCmd
|
||||
}
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
package commonflags
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"k8s.io/klog"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/feature"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// --run-on is considered experimental for now. As such, to exist, it requires the ODO_EXPERIMENTAL_MODE env var to be set.
|
||||
os.Setenv(feature.OdoExperimentalModeEnvVar, feature.OdoExperimentalModeTrue)
|
||||
ctx := context.Background()
|
||||
cfg := config.Configuration{
|
||||
OdoExperimentalMode: true,
|
||||
}
|
||||
ctx = envcontext.WithEnvConfig(ctx, cfg)
|
||||
klog.InitFlags(nil)
|
||||
AddOutputFlag()
|
||||
AddRunOnFlag()
|
||||
AddRunOnFlag(ctx)
|
||||
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
|
||||
|
||||
os.Exit(m.Run())
|
||||
|
||||
@@ -3,8 +3,9 @@ package commonflags
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
"github.com/redhat-developer/odo/pkg/odo/cmdline"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -35,7 +36,7 @@ func AddOutputFlag() {
|
||||
|
||||
// CheckMachineReadableOutputCommand performs machine-readable output functions required to
|
||||
// have it work correctly
|
||||
func CheckMachineReadableOutputCommand(cmd *cobra.Command) error {
|
||||
func CheckMachineReadableOutputCommand(envconfig *config.Configuration, cmd *cobra.Command) error {
|
||||
|
||||
// Get the needed values
|
||||
outputFlag := pflag.Lookup(OutputFlagName)
|
||||
@@ -71,8 +72,8 @@ func CheckMachineReadableOutputCommand(cmd *cobra.Command) error {
|
||||
// Override the logging level by the value (if set) by the ODO_LOG_LEVEL env
|
||||
// The "-v" flag set on command line will take precedence over ODO_LOG_LEVEL env
|
||||
v := flag.CommandLine.Lookup("v").Value.String()
|
||||
if level, ok := os.LookupEnv("ODO_LOG_LEVEL"); ok && v == "0" {
|
||||
_ = flag.CommandLine.Set("v", level)
|
||||
if envconfig.OdoLogLevel != nil && v == "0" {
|
||||
_ = flag.CommandLine.Set("v", strconv.Itoa(*envconfig.OdoLogLevel))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestUseOutputFlagOK(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Set error should be nil but is %v", err)
|
||||
}
|
||||
err = CheckMachineReadableOutputCommand(cmd)
|
||||
err = CheckMachineReadableOutputCommand(nil, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("Check error should be nil but is %v", err)
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func TestUseOutputFlagNotUsed(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Set error should be nil but is %v", err)
|
||||
}
|
||||
err = CheckMachineReadableOutputCommand(cmd)
|
||||
err = CheckMachineReadableOutputCommand(nil, cmd)
|
||||
if err.Error() != "Machine readable output is not yet implemented for this command" {
|
||||
t.Errorf("Check error is %v", err)
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func TestUseOutputFlagWrongValue(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Set error should be nil but is %v", err)
|
||||
}
|
||||
err = CheckMachineReadableOutputCommand(cmd)
|
||||
err = CheckMachineReadableOutputCommand(nil, cmd)
|
||||
if err.Error() != "Please input a valid output format for -o, available format: json" {
|
||||
t.Errorf("Check error is %v", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package commonflags
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -32,8 +33,8 @@ func UseRunOnFlag(cmd *cobra.Command) {
|
||||
// We use "flag" in order to make this accessible throughtout ALL of odo, rather than the
|
||||
// above traditional "persistentflags" usage that does not make it a pointer within the 'pflag'
|
||||
// package
|
||||
func AddRunOnFlag() {
|
||||
if feature.IsEnabled(feature.GenericRunOnFlag) {
|
||||
func AddRunOnFlag(ctx context.Context) {
|
||||
if feature.IsEnabled(ctx, feature.GenericRunOnFlag) {
|
||||
flag.CommandLine.String(RunOnFlagName, "", `Specify target platform, supported platforms: "cluster" (default), "podman" (experimental)`)
|
||||
_ = pflag.CommandLine.MarkHidden(RunOnFlagName)
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ const (
|
||||
var subdeps map[string][]string = map[string][]string{
|
||||
ALIZER: {REGISTRY},
|
||||
DELETE_COMPONENT: {KUBERNETES, EXEC},
|
||||
DEPLOY: {KUBERNETES},
|
||||
DEPLOY: {KUBERNETES, FILESYSTEM},
|
||||
DEV: {BINDING, EXEC, FILESYSTEM, KUBERNETES, PORT_FORWARD, PREFERENCE, SYNC, WATCH},
|
||||
EXEC: {KUBERNETES},
|
||||
INIT: {ALIZER, FILESYSTEM, PREFERENCE, REGISTRY},
|
||||
@@ -153,7 +153,7 @@ func Fetch(command *cobra.Command, platform string) (*Clientset, error) {
|
||||
}
|
||||
}
|
||||
if isDefined(command, PREFERENCE) {
|
||||
dep.PreferenceClient, err = preference.NewClient()
|
||||
dep.PreferenceClient, err = preference.NewClient(command.Context())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func Fetch(command *cobra.Command, platform string) (*Clientset, error) {
|
||||
dep.DeleteClient = _delete.NewDeleteComponentClient(dep.KubernetesClient, dep.ExecClient)
|
||||
}
|
||||
if isDefined(command, DEPLOY) {
|
||||
dep.DeployClient = deploy.NewDeployClient(dep.KubernetesClient)
|
||||
dep.DeployClient = deploy.NewDeployClient(dep.KubernetesClient, dep.FS)
|
||||
}
|
||||
if isDefined(command, INIT) {
|
||||
dep.InitClient = _init.NewInitClient(dep.FS, dep.PreferenceClient, dep.RegistryClient, dep.AlizerClient)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package genericclioptions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/devfile/library/pkg/devfile/parser"
|
||||
"github.com/redhat-developer/odo/pkg/devfile/location"
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
@@ -12,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
// runPreInit executes the Init command before running the main command
|
||||
func runPreInit(workingDir string, deps *clientset.Clientset, cmdline cmdline.Cmdline, msg string) error {
|
||||
func runPreInit(ctx context.Context, workingDir string, deps *clientset.Clientset, cmdline cmdline.Cmdline, msg string) error {
|
||||
isEmptyDir, err := location.DirIsEmpty(deps.FS, workingDir)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -23,7 +25,7 @@ func runPreInit(workingDir string, deps *clientset.Clientset, cmdline cmdline.Cm
|
||||
|
||||
initFlags := deps.InitClient.GetFlags(cmdline.GetFlags())
|
||||
|
||||
err = deps.InitClient.InitDevfile(initFlags, workingDir,
|
||||
err = deps.InitClient.InitDevfile(ctx, initFlags, workingDir,
|
||||
func(interactiveMode bool) {
|
||||
scontext.SetInteractive(cmdline.Context(), interactiveMode)
|
||||
if interactiveMode {
|
||||
|
||||
@@ -29,7 +29,9 @@ import (
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/ui"
|
||||
|
||||
"k8s.io/klog"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
fcontext "github.com/redhat-developer/odo/pkg/odo/commonflags/context"
|
||||
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
@@ -78,14 +80,23 @@ const (
|
||||
)
|
||||
|
||||
func GenericRun(o Runnable, cmd *cobra.Command, args []string) {
|
||||
var err error
|
||||
startTime := time.Now()
|
||||
cfg, _ := preference.NewClient()
|
||||
var (
|
||||
err error
|
||||
startTime = time.Now()
|
||||
ctx = cmd.Context()
|
||||
)
|
||||
|
||||
userConfig, _ := preference.NewClient(ctx)
|
||||
envConfig := envcontext.GetEnvConfig(ctx)
|
||||
|
||||
//lint:ignore SA1019 We deprecated this env var, but until it is removed, we still need to support it
|
||||
disableTelemetryValue, disableTelemetryEnvSet := os.LookupEnv(segment.DisableTelemetryEnv)
|
||||
disableTelemetry, _ := strconv.ParseBool(disableTelemetryValue)
|
||||
debugTelemetry := segment.GetDebugTelemetryFile()
|
||||
isTrackingConsentEnabled, trackingConsentEnvSet, trackingConsentErr := segment.IsTrackingConsentEnabled()
|
||||
disableTelemetryEnvSet := envConfig.OdoDisableTelemetry != nil
|
||||
var disableTelemetry bool
|
||||
if disableTelemetryEnvSet {
|
||||
disableTelemetry = *envConfig.OdoDisableTelemetry
|
||||
}
|
||||
debugTelemetry := pointer.StringDeref(envConfig.OdoDebugTelemetryFile, "")
|
||||
trackingConsentValue, isTrackingConsentEnabled, trackingConsentEnvSet, trackingConsentErr := segment.IsTrackingConsentEnabled(&envConfig)
|
||||
|
||||
// check for conflicting settings
|
||||
if trackingConsentErr == nil && disableTelemetryEnvSet && trackingConsentEnvSet && disableTelemetry == isTrackingConsentEnabled {
|
||||
@@ -98,7 +109,7 @@ func GenericRun(o Runnable, cmd *cobra.Command, args []string) {
|
||||
// Prompt the user to consent for telemetry if a value is not set already
|
||||
// Skip prompting if the preference command is called
|
||||
// This prompt has been placed here so that it does not prompt the user when they call --help
|
||||
if !cfg.IsSet(preference.ConsentTelemetrySetting) && cmd.Parent().Name() != "preference" {
|
||||
if !userConfig.IsSet(preference.ConsentTelemetrySetting) && cmd.Parent().Name() != "preference" {
|
||||
if !segment.RunningInTerminal() {
|
||||
klog.V(4).Infof("Skipping telemetry question because there is no terminal (tty)\n")
|
||||
} else {
|
||||
@@ -107,15 +118,14 @@ func GenericRun(o Runnable, cmd *cobra.Command, args []string) {
|
||||
klog.V(4).Infof("error in determining value of tracking consent env var: %v", trackingConsentErr)
|
||||
askConsent = true
|
||||
} else if trackingConsentEnvSet {
|
||||
trackingConsent := os.Getenv(segment.TrackingConsentEnv)
|
||||
if isTrackingConsentEnabled {
|
||||
klog.V(4).Infof("Skipping telemetry question due to %s=%s\n", segment.TrackingConsentEnv, trackingConsent)
|
||||
klog.V(4).Infof("Skipping telemetry question due to %s=%s\n", segment.TrackingConsentEnv, trackingConsentValue)
|
||||
klog.V(4).Info("Telemetry is enabled!\n")
|
||||
if err1 := cfg.SetConfiguration(preference.ConsentTelemetrySetting, "true"); err1 != nil {
|
||||
if err1 := userConfig.SetConfiguration(preference.ConsentTelemetrySetting, "true"); err1 != nil {
|
||||
klog.V(4).Info(err1.Error())
|
||||
}
|
||||
} else {
|
||||
klog.V(4).Infof("Skipping telemetry question due to %s=%s\n", segment.TrackingConsentEnv, trackingConsent)
|
||||
klog.V(4).Infof("Skipping telemetry question due to %s=%s\n", segment.TrackingConsentEnv, trackingConsentValue)
|
||||
}
|
||||
} else if disableTelemetry {
|
||||
//lint:ignore SA1019 We deprecated this env var, but until it is removed, we still need to support it
|
||||
@@ -129,7 +139,7 @@ func GenericRun(o Runnable, cmd *cobra.Command, args []string) {
|
||||
err = survey.AskOne(prompt, &consentTelemetry, nil)
|
||||
ui.HandleError(err)
|
||||
if err == nil {
|
||||
if err1 := cfg.SetConfiguration(preference.ConsentTelemetrySetting, strconv.FormatBool(consentTelemetry)); err1 != nil {
|
||||
if err1 := userConfig.SetConfiguration(preference.ConsentTelemetrySetting, strconv.FormatBool(consentTelemetry)); err1 != nil {
|
||||
klog.V(4).Info(err1.Error())
|
||||
}
|
||||
}
|
||||
@@ -140,14 +150,15 @@ func GenericRun(o Runnable, cmd *cobra.Command, args []string) {
|
||||
klog.V(4).Infof("WARNING: debug telemetry, if enabled, will be logged in %s", debugTelemetry)
|
||||
}
|
||||
|
||||
err = scontext.SetCaller(cmd.Context(), os.Getenv(segment.TelemetryCaller))
|
||||
// We can dereference as there is a default value defined for this config field
|
||||
err = scontext.SetCaller(cmd.Context(), envConfig.TelemetryCaller)
|
||||
if err != nil {
|
||||
klog.V(3).Infof("error handling caller property for telemetry: %v", err)
|
||||
}
|
||||
|
||||
scontext.SetFlags(cmd.Context(), cmd.Flags())
|
||||
// set value for telemetry status in context so that we do not need to call IsTelemetryEnabled every time to check its status
|
||||
scontext.SetTelemetryStatus(cmd.Context(), segment.IsTelemetryEnabled(cfg))
|
||||
scontext.SetTelemetryStatus(cmd.Context(), segment.IsTelemetryEnabled(userConfig, envConfig))
|
||||
|
||||
// Send data to telemetry in case of user interrupt
|
||||
captureSignals := []os.Signal{syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT, os.Interrupt}
|
||||
@@ -163,7 +174,7 @@ func GenericRun(o Runnable, cmd *cobra.Command, args []string) {
|
||||
startTelemetry(cmd, err, startTime)
|
||||
})
|
||||
|
||||
util.LogErrorAndExit(commonflags.CheckMachineReadableOutputCommand(cmd), "")
|
||||
util.LogErrorAndExit(commonflags.CheckMachineReadableOutputCommand(&envConfig, cmd), "")
|
||||
util.LogErrorAndExit(commonflags.CheckRunOnCommand(cmd), "")
|
||||
util.LogErrorAndExit(commonflags.CheckVariablesCommand(cmd), "")
|
||||
|
||||
@@ -175,7 +186,6 @@ func GenericRun(o Runnable, cmd *cobra.Command, args []string) {
|
||||
}
|
||||
o.SetClientset(deps)
|
||||
|
||||
ctx := cmdLineObj.Context()
|
||||
ctx = fcontext.WithJsonOutput(ctx, commonflags.GetJsonOutputValue(cmdLineObj))
|
||||
ctx = fcontext.WithRunOn(ctx, platform)
|
||||
ctx = odocontext.WithApplication(ctx, defaultAppName)
|
||||
@@ -201,7 +211,7 @@ func GenericRun(o Runnable, cmd *cobra.Command, args []string) {
|
||||
|
||||
if preiniter, ok := o.(PreIniter); ok {
|
||||
msg := preiniter.PreInit()
|
||||
err = runPreInit(cwd, deps, cmdLineObj, msg)
|
||||
err = runPreInit(ctx, cwd, deps, cmdLineObj, msg)
|
||||
if err != nil {
|
||||
startTelemetry(cmd, err, startTime)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package preference
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/log"
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/ui"
|
||||
"github.com/redhat-developer/odo/pkg/util"
|
||||
@@ -69,9 +71,10 @@ type preferenceInfo struct {
|
||||
|
||||
var _ Client = (*preferenceInfo)(nil)
|
||||
|
||||
func getPreferenceFile() (string, error) {
|
||||
if env, ok := os.LookupEnv(GlobalConfigEnvName); ok {
|
||||
return env, nil
|
||||
func getPreferenceFile(ctx context.Context) (string, error) {
|
||||
envConfig := envcontext.GetEnvConfig(ctx)
|
||||
if envConfig.Globalodoconfig != nil {
|
||||
return *envConfig.Globalodoconfig, nil
|
||||
}
|
||||
|
||||
if len(customHomeDir) != 0 {
|
||||
@@ -85,8 +88,8 @@ func getPreferenceFile() (string, error) {
|
||||
return filepath.Join(currentUser.HomeDir, ".odo", configFileName), nil
|
||||
}
|
||||
|
||||
func NewClient() (Client, error) {
|
||||
return newPreferenceInfo()
|
||||
func NewClient(ctx context.Context) (Client, error) {
|
||||
return newPreferenceInfo(ctx)
|
||||
}
|
||||
|
||||
// newPreference creates an empty Preference struct with type meta information
|
||||
@@ -101,8 +104,8 @@ func newPreference() Preference {
|
||||
|
||||
// newPreferenceInfo gets the PreferenceInfo from preference file
|
||||
// or returns default PreferenceInfo if preference file does not exist
|
||||
func newPreferenceInfo() (*preferenceInfo, error) {
|
||||
preferenceFile, err := getPreferenceFile()
|
||||
func newPreferenceInfo(ctx context.Context) (*preferenceInfo, error) {
|
||||
preferenceFile, err := getPreferenceFile(ctx)
|
||||
klog.V(4).Infof("The path for preference file is %+v", preferenceFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package preference
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -9,6 +10,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@@ -19,7 +23,7 @@ func TestNew(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tempConfigFile.Close()
|
||||
t.Setenv(GlobalConfigEnvName, tempConfigFile.Name())
|
||||
tempConfigFileName := tempConfigFile.Name()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -52,7 +56,11 @@ func TestNew(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
cfi, err := newPreferenceInfo()
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{
|
||||
Globalodoconfig: &tempConfigFileName,
|
||||
})
|
||||
cfi, err := newPreferenceInfo(ctx)
|
||||
switch test.success {
|
||||
case true:
|
||||
if err != nil {
|
||||
@@ -72,12 +80,6 @@ func TestNew(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetPushTimeout(t *testing.T) {
|
||||
tempConfigFile, err := ioutil.TempFile("", "odoconfig")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tempConfigFile.Close()
|
||||
t.Setenv(GlobalConfigEnvName, tempConfigFile.Name())
|
||||
|
||||
nonzeroValue := 5 * time.Second
|
||||
|
||||
@@ -103,7 +105,9 @@ func TestGetPushTimeout(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg, err := newPreferenceInfo()
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{})
|
||||
cfg, err := newPreferenceInfo(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -118,12 +122,6 @@ func TestGetPushTimeout(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTimeout(t *testing.T) {
|
||||
tempConfigFile, err := ioutil.TempFile("", "odoconfig")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tempConfigFile.Close()
|
||||
t.Setenv(GlobalConfigEnvName, tempConfigFile.Name())
|
||||
zeroValue := 0 * time.Second
|
||||
nonzeroValue := 5 * time.Second
|
||||
tests := []struct {
|
||||
@@ -159,7 +157,9 @@ func TestGetTimeout(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg, err := newPreferenceInfo()
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{})
|
||||
cfg, err := newPreferenceInfo(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -174,12 +174,6 @@ func TestGetTimeout(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetConfiguration(t *testing.T) {
|
||||
tempConfigFile, err := ioutil.TempFile("", "odoconfig")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tempConfigFile.Close()
|
||||
t.Setenv(GlobalConfigEnvName, tempConfigFile.Name())
|
||||
trueValue := true
|
||||
falseValue := false
|
||||
minValue := minimumDurationValue
|
||||
@@ -357,7 +351,9 @@ func TestSetConfiguration(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg, err := newPreferenceInfo()
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{})
|
||||
cfg, err := newPreferenceInfo(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -402,12 +398,6 @@ func TestSetConfiguration(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestConsentTelemetry(t *testing.T) {
|
||||
tempConfigFile, err := ioutil.TempFile("", "odoconfig")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tempConfigFile.Close()
|
||||
t.Setenv(GlobalConfigEnvName, tempConfigFile.Name())
|
||||
trueValue := true
|
||||
falseValue := false
|
||||
|
||||
@@ -457,13 +447,6 @@ func TestConsentTelemetry(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetupdateNotification(t *testing.T) {
|
||||
|
||||
tempConfigFile, err := ioutil.TempFile("", "odoconfig")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tempConfigFile.Close()
|
||||
t.Setenv(GlobalConfigEnvName, tempConfigFile.Name())
|
||||
trueValue := true
|
||||
falseValue := false
|
||||
|
||||
@@ -562,13 +545,15 @@ func TestIsSupportedParameter(t *testing.T) {
|
||||
|
||||
func TestPreferenceIsntCreatedWhenOdoIsUsed(t *testing.T) {
|
||||
// cleaning up old odo files if any
|
||||
filename, err := getPreferenceFile()
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{})
|
||||
filename, err := getPreferenceFile(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
os.RemoveAll(filename)
|
||||
|
||||
conf, err := newPreferenceInfo()
|
||||
conf, err := newPreferenceInfo(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("error while creating global preference %v", err)
|
||||
}
|
||||
@@ -578,7 +563,9 @@ func TestPreferenceIsntCreatedWhenOdoIsUsed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMetaTypePopulatedInPreference(t *testing.T) {
|
||||
pi, err := newPreferenceInfo()
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{})
|
||||
pi, err := newPreferenceInfo(ctx)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -687,12 +674,6 @@ func TestHandleWithRegistryExist(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetConsentTelemetry(t *testing.T) {
|
||||
tempConfigFile, err := ioutil.TempFile("", "odoconfig")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tempConfigFile.Close()
|
||||
t.Setenv(GlobalConfigEnvName, tempConfigFile.Name())
|
||||
trueValue := true
|
||||
falseValue := false
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
dfutil "github.com/devfile/library/pkg/util"
|
||||
"github.com/devfile/registry-support/registry-library/library"
|
||||
@@ -13,5 +15,5 @@ type Client interface {
|
||||
DownloadFileInMemory(params dfutil.HTTPRequestParams) ([]byte, error)
|
||||
DownloadStarterProject(starterProject *devfilev1.StarterProject, decryptedToken string, contextDir string, verbose bool) error
|
||||
GetDevfileRegistries(registryName string) ([]api.Registry, error)
|
||||
ListDevfileStacks(registryName, devfileFlag, filterFlag string, detailsFlag bool) (DevfileStackList, error)
|
||||
ListDevfileStacks(ctx context.Context, registryName, devfileFlag, filterFlag string, detailsFlag bool) (DevfileStackList, error)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
v1alpha2 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
|
||||
@@ -82,18 +83,18 @@ func (mr *MockClientMockRecorder) GetDevfileRegistries(registryName interface{})
|
||||
}
|
||||
|
||||
// ListDevfileStacks mocks base method.
|
||||
func (m *MockClient) ListDevfileStacks(registryName, devfileFlag, filterFlag string, detailsFlag bool) (DevfileStackList, error) {
|
||||
func (m *MockClient) ListDevfileStacks(ctx context.Context, registryName, devfileFlag, filterFlag string, detailsFlag bool) (DevfileStackList, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListDevfileStacks", registryName, devfileFlag, filterFlag, detailsFlag)
|
||||
ret := m.ctrl.Call(m, "ListDevfileStacks", ctx, registryName, devfileFlag, filterFlag, detailsFlag)
|
||||
ret0, _ := ret[0].(DevfileStackList)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListDevfileStacks indicates an expected call of ListDevfileStacks.
|
||||
func (mr *MockClientMockRecorder) ListDevfileStacks(registryName, devfileFlag, filterFlag, detailsFlag interface{}) *gomock.Call {
|
||||
func (mr *MockClientMockRecorder) ListDevfileStacks(ctx, registryName, devfileFlag, filterFlag, detailsFlag interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDevfileStacks", reflect.TypeOf((*MockClient)(nil).ListDevfileStacks), registryName, devfileFlag, filterFlag, detailsFlag)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDevfileStacks", reflect.TypeOf((*MockClient)(nil).ListDevfileStacks), ctx, registryName, devfileFlag, filterFlag, detailsFlag)
|
||||
}
|
||||
|
||||
// PullStackFromRegistry mocks base method.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
@@ -89,7 +90,7 @@ func (o RegistryClient) GetDevfileRegistries(registryName string) ([]api.Registr
|
||||
}
|
||||
|
||||
// ListDevfileStacks lists all the available devfile stacks in devfile registry
|
||||
func (o RegistryClient) ListDevfileStacks(registryName, devfileFlag, filterFlag string, detailsFlag bool) (DevfileStackList, error) {
|
||||
func (o RegistryClient) ListDevfileStacks(ctx context.Context, registryName, devfileFlag, filterFlag string, detailsFlag bool) (DevfileStackList, error) {
|
||||
catalogDevfileList := &DevfileStackList{}
|
||||
var err error
|
||||
|
||||
@@ -115,7 +116,7 @@ func (o RegistryClient) ListDevfileStacks(registryName, devfileFlag, filterFlag
|
||||
registry := reg // Needed to prevent the lambda from capturing the value
|
||||
registryPriority := regPriority // Needed to prevent the lambda from capturing the value
|
||||
retrieveRegistryIndices.Add(util.ConcurrentTask{ToRun: func(errChannel chan error) {
|
||||
registryDevfiles, err := getRegistryStacks(o.preferenceClient, registry)
|
||||
registryDevfiles, err := getRegistryStacks(ctx, o.preferenceClient, registry)
|
||||
if err != nil {
|
||||
log.Warningf("Registry %s is not set up properly with error: %v, please check the registry URL, and credential and remove add the registry again (refer to `odo preference add registry --help`)\n", registry.Name, err)
|
||||
return
|
||||
@@ -158,7 +159,7 @@ func (o RegistryClient) ListDevfileStacks(registryName, devfileFlag, filterFlag
|
||||
}
|
||||
|
||||
if detailsFlag {
|
||||
devfileData, err := o.retrieveDevfileDataFromRegistry(devfile.Registry.Name, devfile.Name)
|
||||
devfileData, err := o.retrieveDevfileDataFromRegistry(ctx, devfile.Registry.Name, devfile.Name)
|
||||
if err != nil {
|
||||
return *catalogDevfileList, err
|
||||
}
|
||||
@@ -185,7 +186,7 @@ func (o RegistryClient) ListDevfileStacks(registryName, devfileFlag, filterFlag
|
||||
}
|
||||
|
||||
// getRegistryStacks retrieves the registry's index devfile stack entries
|
||||
func getRegistryStacks(preferenceClient preference.Client, registry api.Registry) ([]api.DevfileStack, error) {
|
||||
func getRegistryStacks(ctx context.Context, preferenceClient preference.Client, registry api.Registry) ([]api.DevfileStack, error) {
|
||||
isGithubregistry, err := IsGithubBasedRegistry(registry.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -194,7 +195,7 @@ func getRegistryStacks(preferenceClient preference.Client, registry api.Registry
|
||||
return nil, &ErrGithubRegistryNotSupported{}
|
||||
}
|
||||
// OCI-based registry
|
||||
devfileIndex, err := library.GetRegistryIndex(registry.URL, segment.GetRegistryOptions(), indexSchema.StackDevfileType)
|
||||
devfileIndex, err := library.GetRegistryIndex(registry.URL, segment.GetRegistryOptions(ctx), indexSchema.StackDevfileType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -221,7 +222,7 @@ func createRegistryDevfiles(registry api.Registry, devfileIndex []indexSchema.Sc
|
||||
return registryDevfiles, nil
|
||||
}
|
||||
|
||||
func (o RegistryClient) retrieveDevfileDataFromRegistry(registryName string, devfileName string) (api.DevfileData, error) {
|
||||
func (o RegistryClient) retrieveDevfileDataFromRegistry(ctx context.Context, registryName string, devfileName string) (api.DevfileData, error) {
|
||||
|
||||
// Create random temporary file
|
||||
tmpFile, err := ioutil.TempDir("", "odo")
|
||||
@@ -241,7 +242,7 @@ func (o RegistryClient) retrieveDevfileDataFromRegistry(registryName string, dev
|
||||
// 4. We need to read the file from the temporary file, unmarshal it and then return the devfile data
|
||||
for _, reg = range registries {
|
||||
if reg.Name == registryName {
|
||||
err = o.PullStackFromRegistry(reg.URL, devfileName, tmpFile, segment.GetRegistryOptions())
|
||||
err = o.PullStackFromRegistry(reg.URL, devfileName, tmpFile, segment.GetRegistryOptions(ctx))
|
||||
if err != nil {
|
||||
return api.DevfileData{}, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -10,7 +11,10 @@ import (
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/api"
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
|
||||
)
|
||||
@@ -35,8 +39,7 @@ OdoSettings:
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
t.Setenv(preference.GlobalConfigEnvName, tempConfigFile.Name())
|
||||
tempConfigFileName := tempConfigFile.Name()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -74,7 +77,11 @@ OdoSettings:
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
prefClient, _ := preference.NewClient()
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{
|
||||
Globalodoconfig: &tempConfigFileName,
|
||||
})
|
||||
prefClient, _ := preference.NewClient(ctx)
|
||||
catClient := NewRegistryClient(filesystem.NewFakeFs(), prefClient)
|
||||
got, err := catClient.GetDevfileRegistries(tt.registryName)
|
||||
if err != nil {
|
||||
@@ -260,7 +267,9 @@ func TestListDevfileStacks(t *testing.T) {
|
||||
},
|
||||
}).AnyTimes()
|
||||
catClient := NewRegistryClient(filesystem.NewFakeFs(), prefClient)
|
||||
got, err := catClient.ListDevfileStacks(tt.registryName, tt.devfileName, tt.filter, false)
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{})
|
||||
got, err := catClient.ListDevfileStacks(ctx, tt.registryName, tt.devfileName, tt.filter, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -336,7 +345,9 @@ func TestGetRegistryDevfiles(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
prefClient := preference.NewMockClient(ctrl)
|
||||
got, err := getRegistryStacks(prefClient, tt.registry)
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{})
|
||||
got, err := getRegistryStacks(ctx, prefClient, tt.registry)
|
||||
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Got: %v, want: %v", got, tt.want)
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
)
|
||||
|
||||
const (
|
||||
// GlobalConfigEnvName is the environment variable GLOBALODOCONFIG
|
||||
GlobalConfigEnvName = "GLOBALODOCONFIG"
|
||||
)
|
||||
|
||||
func TestIsSecure(t *testing.T) {
|
||||
tempConfigFile, err := ioutil.TempFile("", "odoconfig")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tempConfigFile.Close()
|
||||
t.Setenv(GlobalConfigEnvName, tempConfigFile.Name())
|
||||
tempConfigFileName := tempConfigFile.Name()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -52,7 +50,11 @@ func TestIsSecure(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg, err := preference.NewClient()
|
||||
ctx := context.Background()
|
||||
ctx = envcontext.WithEnvConfig(ctx, config.Configuration{
|
||||
Globalodoconfig: &tempConfigFileName,
|
||||
})
|
||||
cfg, err := preference.NewClient(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("Unable to get preference file with error: %v", err)
|
||||
}
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
package segment
|
||||
|
||||
import (
|
||||
"github.com/Xuanwo/go-locale"
|
||||
registryLibrary "github.com/devfile/registry-support/registry-library/library"
|
||||
"k8s.io/klog"
|
||||
"context"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
"github.com/Xuanwo/go-locale"
|
||||
|
||||
registryLibrary "github.com/devfile/registry-support/registry-library/library"
|
||||
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
scontext "github.com/redhat-developer/odo/pkg/segment/context"
|
||||
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// getTelemetryForDevfileRegistry returns a populated TelemetryData object that contains some odo telemetry (with client consent), such as the anonymous ID and
|
||||
// locale in addition to the generic client name "odo"
|
||||
|
||||
func getTelemetryForDevfileRegistry() (registryLibrary.TelemetryData, error) {
|
||||
func getTelemetryForDevfileRegistry(ctx context.Context) (registryLibrary.TelemetryData, error) {
|
||||
|
||||
td := registryLibrary.TelemetryData{
|
||||
Client: TelemetryClient,
|
||||
}
|
||||
|
||||
if GetDebugTelemetryFile() != "" {
|
||||
envConfig := envcontext.GetEnvConfig(ctx)
|
||||
if envConfig.OdoDebugTelemetryFile != nil {
|
||||
return td, nil
|
||||
}
|
||||
|
||||
// TODO(feloy) Get from DI
|
||||
cfg, err := preference.NewClient()
|
||||
if err != nil {
|
||||
return td, err
|
||||
}
|
||||
|
||||
if !IsTelemetryEnabled(cfg) {
|
||||
if !scontext.GetTelemetryStatus(ctx) {
|
||||
return td, nil
|
||||
}
|
||||
|
||||
@@ -48,8 +47,8 @@ func getTelemetryForDevfileRegistry() (registryLibrary.TelemetryData, error) {
|
||||
}
|
||||
|
||||
// GetRegistryOptions returns a populated RegistryOptions object containing all the properties needed to make a devfile registry library call
|
||||
func GetRegistryOptions() registryLibrary.RegistryOptions {
|
||||
td, err := getTelemetryForDevfileRegistry()
|
||||
func GetRegistryOptions(ctx context.Context) registryLibrary.RegistryOptions {
|
||||
td, err := getTelemetryForDevfileRegistry(ctx)
|
||||
if err != nil {
|
||||
// this error should not prevent basic telemetry from being sent
|
||||
klog.Errorf("An error prevented additional telemetry to be set %v", err)
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
package segment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/devfile/registry-support/registry-library/library"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
scontext "github.com/redhat-developer/odo/pkg/segment/context"
|
||||
)
|
||||
|
||||
func TestGetRegistryOptions(t *testing.T) {
|
||||
@@ -21,50 +27,45 @@ func TestGetRegistryOptions(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
testName string
|
||||
consent string
|
||||
consent bool
|
||||
telemetryFile bool
|
||||
cfg preference.Client
|
||||
}{
|
||||
{
|
||||
testName: "Registry options with telemetry consent and telemetry file",
|
||||
consent: "true",
|
||||
consent: true,
|
||||
telemetryFile: true,
|
||||
},
|
||||
{
|
||||
testName: "Registry options with telemetry consent and no telemetry file",
|
||||
consent: "true",
|
||||
consent: true,
|
||||
telemetryFile: false,
|
||||
},
|
||||
|
||||
{
|
||||
testName: "Registry options without telemetry consent and telemetry file",
|
||||
consent: "false",
|
||||
consent: false,
|
||||
telemetryFile: true,
|
||||
},
|
||||
{
|
||||
testName: "Registry options without telemetry consent and no telemetry file",
|
||||
consent: "false",
|
||||
consent: false,
|
||||
telemetryFile: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.testName, func(t *testing.T) {
|
||||
cfg, err := preference.NewClient()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = cfg.SetConfiguration(preference.ConsentTelemetrySetting, tt.consent)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
ctx := scontext.NewContext(context.Background())
|
||||
var envConfig config.Configuration
|
||||
if tt.telemetryFile {
|
||||
t.Setenv(DebugTelemetryFileEnv, "/a/telemetry/file")
|
||||
envConfig.OdoDebugTelemetryFile = pointer.String("/a/telemetry/file")
|
||||
}
|
||||
ctx = envcontext.WithEnvConfig(ctx, envConfig)
|
||||
scontext.SetTelemetryStatus(ctx, tt.consent)
|
||||
|
||||
ro := GetRegistryOptions()
|
||||
err = verifyRegistryOptions(cfg.GetConsentTelemetry(), tt.telemetryFile, ro)
|
||||
ro := GetRegistryOptions(ctx)
|
||||
err = verifyRegistryOptions(tt.consent, tt.telemetryFile, ro)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package segment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@@ -10,18 +11,19 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Xuanwo/go-locale"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
scontext "github.com/redhat-developer/odo/pkg/segment/context"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
"golang.org/x/term"
|
||||
"gopkg.in/segmentio/analytics-go.v3"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
)
|
||||
@@ -47,9 +49,7 @@ const (
|
||||
// Setting it to 'no' has the same effect as DisableTelemetryEnv=true (telemetry is disabled and no question asked)
|
||||
// Settings this to 'yes' skips the question about telemetry and enables user tracking.
|
||||
// Possible values are yes/no.
|
||||
TrackingConsentEnv = "ODO_TRACKING_CONSENT"
|
||||
DebugTelemetryFileEnv = "ODO_DEBUG_TELEMETRY_FILE"
|
||||
TelemetryCaller = "TELEMETRY_CALLER"
|
||||
TrackingConsentEnv = "ODO_TRACKING_CONSENT"
|
||||
)
|
||||
|
||||
type TelemetryProperties struct {
|
||||
@@ -70,22 +70,20 @@ type TelemetryData struct {
|
||||
type Client struct {
|
||||
// SegmentClient helps interact with the segment API
|
||||
SegmentClient analytics.Client
|
||||
// Preference points to the global odo config
|
||||
Preference preference.Client
|
||||
// TelemetryFilePath points to the file containing anonymousID used for tracking odo commands executed by the user
|
||||
TelemetryFilePath string
|
||||
}
|
||||
|
||||
// NewClient returns a Client created with the default args
|
||||
func NewClient(preference preference.Client) (*Client, error) {
|
||||
return newCustomClient(preference,
|
||||
func NewClient() (*Client, error) {
|
||||
return newCustomClient(
|
||||
GetTelemetryFilePath(),
|
||||
analytics.DefaultEndpoint,
|
||||
)
|
||||
}
|
||||
|
||||
// newCustomClient returns a Client created with custom args
|
||||
func newCustomClient(preference preference.Client, telemetryFilePath string, segmentEndpoint string) (*Client, error) {
|
||||
func newCustomClient(telemetryFilePath string, segmentEndpoint string) (*Client, error) {
|
||||
// get the locale information
|
||||
tag, err := locale.Detect()
|
||||
if err != nil {
|
||||
@@ -109,7 +107,6 @@ func newCustomClient(preference preference.Client, telemetryFilePath string, seg
|
||||
}
|
||||
return &Client{
|
||||
SegmentClient: client,
|
||||
Preference: preference,
|
||||
TelemetryFilePath: telemetryFilePath,
|
||||
}, nil
|
||||
}
|
||||
@@ -128,9 +125,9 @@ func (c *Client) Close() error {
|
||||
}
|
||||
|
||||
// Upload prepares the data to be sent to segment and send it once the client connection closes
|
||||
func (c *Client) Upload(data TelemetryData) error {
|
||||
func (c *Client) Upload(ctx context.Context, data TelemetryData) error {
|
||||
// if the user has not consented for telemetry, return
|
||||
if !IsTelemetryEnabled(c.Preference) {
|
||||
if !scontext.GetTelemetryStatus(ctx) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -264,20 +261,20 @@ func RunningInTerminal() bool {
|
||||
}
|
||||
|
||||
// IsTelemetryEnabled returns true if user has consented to telemetry
|
||||
func IsTelemetryEnabled(cfg preference.Client) bool {
|
||||
func IsTelemetryEnabled(cfg preference.Client, envConfig config.Configuration) bool {
|
||||
klog.V(4).Info("Checking telemetry enable status")
|
||||
// The env variable gets precedence in this decision.
|
||||
// In case a non-bool value was passed to the env var, we ignore it
|
||||
|
||||
//lint:ignore SA1019 We deprecated this env var, but until it is removed, we still need to support it
|
||||
disableTelemetry, _ := strconv.ParseBool(os.Getenv(DisableTelemetryEnv))
|
||||
disableTelemetry := pointer.BoolDeref(envConfig.OdoDisableTelemetry, false)
|
||||
if disableTelemetry {
|
||||
//lint:ignore SA1019 We deprecated this env var, but until it is removed, we still need to support it
|
||||
klog.V(4).Infof("Sending telemetry disabled by %q env variable\n", DisableTelemetryEnv)
|
||||
return false
|
||||
}
|
||||
|
||||
trackingConsentEnabled, present, err := IsTrackingConsentEnabled()
|
||||
_, trackingConsentEnabled, present, err := IsTrackingConsentEnabled(&envConfig)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("error in determining value of tracking consent env var: %v", err)
|
||||
} else if present {
|
||||
@@ -301,18 +298,18 @@ func IsTelemetryEnabled(cfg preference.Client) bool {
|
||||
|
||||
// IsTrackingConsentEnabled returns whether tracking consent is enabled, based on the value of the TrackingConsentEnv environment variable.
|
||||
// The second value returned indicates whether the variable is present in the environment.
|
||||
func IsTrackingConsentEnabled() (enabled bool, present bool, err error) {
|
||||
trackingConsent, ok := os.LookupEnv(TrackingConsentEnv)
|
||||
if !ok {
|
||||
return false, false, nil
|
||||
func IsTrackingConsentEnabled(envConfig *config.Configuration) (value string, enabled bool, present bool, err error) {
|
||||
if envConfig.OdoTrackingConsent == nil {
|
||||
return "", false, false, nil
|
||||
}
|
||||
trackingConsent := *envConfig.OdoTrackingConsent
|
||||
switch trackingConsent {
|
||||
case "yes":
|
||||
return true, true, nil
|
||||
return trackingConsent, true, true, nil
|
||||
case "no":
|
||||
return false, true, nil
|
||||
return trackingConsent, false, true, nil
|
||||
default:
|
||||
return false, true, fmt.Errorf("invalid value for %s: %q", TrackingConsentEnv, trackingConsent)
|
||||
return trackingConsent, false, true, fmt.Errorf("invalid value for %s: %q", TrackingConsentEnv, trackingConsent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +352,3 @@ func sanitizeExec(errString string) string {
|
||||
errString = pattern.ReplaceAllString(errString, fmt.Sprintf("exec command %s", Sanitizer))
|
||||
return errString
|
||||
}
|
||||
|
||||
func GetDebugTelemetryFile() string {
|
||||
return os.Getenv(DebugTelemetryFileEnv)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
"github.com/redhat-developer/odo/pkg/kclient"
|
||||
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
|
||||
|
||||
@@ -66,11 +67,7 @@ func TestClientUploadWithoutConsent(t *testing.T) {
|
||||
defer server.Close()
|
||||
defer close(body)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
cfg := preference.NewMockClient(ctrl)
|
||||
cfg.EXPECT().GetConsentTelemetry().Return(false)
|
||||
|
||||
c, err := newCustomClient(cfg, createConfigDir(t), server.URL)
|
||||
c, err := newCustomClient(createConfigDir(t), server.URL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -78,7 +75,9 @@ func TestClientUploadWithoutConsent(t *testing.T) {
|
||||
testError := errors.New("error occurred")
|
||||
uploadData := fakeTelemetryData("odo preference view", testError, context.Background())
|
||||
// run a command, odo preference view
|
||||
if err = c.Upload(uploadData); err != nil {
|
||||
ctx := context.Background()
|
||||
scontext.SetTelemetryStatus(ctx, false)
|
||||
if err = c.Upload(ctx, uploadData); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
@@ -124,17 +123,15 @@ func TestClientUploadWithConsent(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Log("Running test: ", tt.testName)
|
||||
t.Run(tt.testName, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
cfg := preference.NewMockClient(ctrl)
|
||||
cfg.EXPECT().GetConsentTelemetry().Return(true)
|
||||
|
||||
c, err := newCustomClient(cfg, createConfigDir(t), server.URL)
|
||||
c, err := newCustomClient(createConfigDir(t), server.URL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
uploadData := fakeTelemetryData("odo init", tt.err, context.Background())
|
||||
// upload the data to Segment
|
||||
if err = c.Upload(uploadData); err != nil {
|
||||
ctx := scontext.NewContext(context.Background())
|
||||
scontext.SetTelemetryStatus(ctx, true)
|
||||
if err = c.Upload(ctx, uploadData); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// segment.Client.SegmentClient uploads the data to server when a condition is met or when the connection is closed.
|
||||
@@ -240,7 +237,7 @@ func TestIsTelemetryEnabled(t *testing.T) {
|
||||
|
||||
// When only ODO_DISABLE_TELEMETRY is present in the env, it takes precedence over the ConsentTelemetry preference,
|
||||
// only if ODO_DISABLE_TELEMETRY=true
|
||||
for _, odoDisableTelemetry := range []string{"", "true", "false", "foo"} {
|
||||
for _, odoDisableTelemetry := range []string{"true", "false"} {
|
||||
for _, consentTelemetry := range []bool{true, false} {
|
||||
odoDisableTelemetry := odoDisableTelemetry
|
||||
consentTelemetry := consentTelemetry
|
||||
@@ -262,7 +259,7 @@ func TestIsTelemetryEnabled(t *testing.T) {
|
||||
}
|
||||
}
|
||||
//Cases where all the environment variables are there.
|
||||
for _, odoDisableTelemetry := range []string{"", "true", "false", "foo"} {
|
||||
for _, odoDisableTelemetry := range []string{"true", "false"} {
|
||||
for _, odoTrackingConsent := range []string{"", "yes", "no", "bar"} {
|
||||
for _, consentTelemetry := range []bool{true, false} {
|
||||
odoDisableTelemetry := odoDisableTelemetry
|
||||
@@ -300,7 +297,11 @@ func TestIsTelemetryEnabled(t *testing.T) {
|
||||
cfg := preference.NewMockClient(ctrl)
|
||||
cfg.EXPECT().GetConsentTelemetry().Return(tt.consentTelemetryPref).AnyTimes()
|
||||
|
||||
got := IsTelemetryEnabled(cfg)
|
||||
envConfig, err := config.GetConfiguration()
|
||||
if err != nil {
|
||||
t.Errorf("Get configuration fails: %v", err)
|
||||
}
|
||||
got := IsTelemetryEnabled(cfg, *envConfig)
|
||||
|
||||
//lint:ignore SA1019 We deprecated this env var, but until it is removed, we still want to test it
|
||||
want := tt.want(tt.env[DisableTelemetryEnv], tt.env[TrackingConsentEnv], tt.consentTelemetryPref)
|
||||
@@ -318,10 +319,8 @@ func TestClientUploadWithContext(t *testing.T) {
|
||||
defer server.Close()
|
||||
defer close(body)
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
cfg := preference.NewMockClient(ctrl)
|
||||
cfg.EXPECT().GetConsentTelemetry().Return(true).AnyTimes()
|
||||
ctx := scontext.NewContext(context.Background())
|
||||
scontext.SetTelemetryStatus(ctx, true)
|
||||
|
||||
for k, v := range map[string]string{scontext.ComponentType: "nodejs", scontext.ClusterType: ""} {
|
||||
switch k {
|
||||
@@ -333,12 +332,12 @@ func TestClientUploadWithContext(t *testing.T) {
|
||||
scontext.SetClusterType(ctx, fakeClient)
|
||||
uploadData = fakeTelemetryData("odo set project", nil, ctx)
|
||||
}
|
||||
c, err := newCustomClient(cfg, createConfigDir(t), server.URL)
|
||||
c, err := newCustomClient(createConfigDir(t), server.URL)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// upload the data to Segment
|
||||
if err = c.Upload(uploadData); err != nil {
|
||||
if err = c.Upload(ctx, uploadData); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err = c.Close(); err != nil {
|
||||
|
||||
@@ -82,7 +82,7 @@ type WatchParameters struct {
|
||||
// Custom function that can be used to push detected changes to remote pod. For more info about what each of the parameters to this function, please refer, pkg/component/component.go#PushLocal
|
||||
// WatchHandler func(kclient.ClientInterface, string, string, string, io.Writer, []string, []string, bool, []string, bool) error
|
||||
// Custom function that can be used to push detected changes to remote devfile pod. For more info about what each of the parameters to this function, please refer, pkg/devfile/adapters/interface.go#PlatformAdapter
|
||||
DevfileWatchHandler func(adapters.PushParameters, WatchParameters, *ComponentStatus) error
|
||||
DevfileWatchHandler func(context.Context, adapters.PushParameters, WatchParameters, *ComponentStatus) error
|
||||
// Parameter whether or not to show build logs
|
||||
Show bool
|
||||
// DevfileBuildCmd takes the build command through the command line and overwrites devfile build command
|
||||
@@ -114,7 +114,7 @@ type evaluateChangesFunc func(events []fsnotify.Event, path string, fileIgnores
|
||||
|
||||
// processEventsFunc processes the events received on the watcher. It uses the WatchParameters to trigger watch handler and writes to out
|
||||
// It returns a Duration after which to recall in case of error
|
||||
type processEventsFunc func(changedFiles, deletedPaths []string, parameters WatchParameters, out io.Writer, componentStatus *ComponentStatus, backoff *ExpBackoff) (*time.Duration, error)
|
||||
type processEventsFunc func(ctx context.Context, changedFiles, deletedPaths []string, parameters WatchParameters, out io.Writer, componentStatus *ComponentStatus, backoff *ExpBackoff) (*time.Duration, error)
|
||||
|
||||
func (o *WatchClient) WatchAndPush(out io.Writer, parameters WatchParameters, ctx context.Context, componentStatus ComponentStatus) error {
|
||||
klog.V(4).Infof("starting WatchAndPush, path: %s, component: %s, ignores %s", parameters.Path, parameters.ComponentName, parameters.FileIgnores)
|
||||
@@ -240,7 +240,7 @@ func (o *WatchClient) eventWatcher(
|
||||
|
||||
componentStatus.State = StateSyncOutdated
|
||||
fmt.Fprintf(out, "Pushing files...\n\n")
|
||||
retry, err := processEventsHandler(changedFiles, deletedPaths, parameters, out, &componentStatus, expBackoff)
|
||||
retry, err := processEventsHandler(ctx, changedFiles, deletedPaths, parameters, out, &componentStatus, expBackoff)
|
||||
o.forceSync = false
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -278,7 +278,7 @@ func (o *WatchClient) eventWatcher(
|
||||
}
|
||||
|
||||
case <-deployTimer.C:
|
||||
retry, err := processEventsHandler(nil, nil, parameters, out, &componentStatus, expBackoff)
|
||||
retry, err := processEventsHandler(ctx, nil, nil, parameters, out, &componentStatus, expBackoff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -294,7 +294,7 @@ func (o *WatchClient) eventWatcher(
|
||||
|
||||
case <-devfileTimer.C:
|
||||
fmt.Fprintf(out, "Updating Component...\n\n")
|
||||
retry, err := processEventsHandler(nil, nil, parameters, out, &componentStatus, expBackoff)
|
||||
retry, err := processEventsHandler(ctx, nil, nil, parameters, out, &componentStatus, expBackoff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -306,7 +306,7 @@ func (o *WatchClient) eventWatcher(
|
||||
}
|
||||
|
||||
case <-retryTimer.C:
|
||||
retry, err := processEventsHandler(nil, nil, parameters, out, &componentStatus, expBackoff)
|
||||
retry, err := processEventsHandler(ctx, nil, nil, parameters, out, &componentStatus, expBackoff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -423,6 +423,7 @@ func evaluateFileChanges(events []fsnotify.Event, path string, fileIgnores []str
|
||||
}
|
||||
|
||||
func (o *WatchClient) processEvents(
|
||||
ctx context.Context,
|
||||
changedFiles, deletedPaths []string,
|
||||
parameters WatchParameters,
|
||||
out io.Writer,
|
||||
@@ -451,7 +452,7 @@ func (o *WatchClient) processEvents(
|
||||
ErrOut: parameters.ErrOut,
|
||||
}
|
||||
oldStatus := *componentStatus
|
||||
err := parameters.DevfileWatchHandler(pushParams, parameters, componentStatus)
|
||||
err := parameters.DevfileWatchHandler(ctx, pushParams, parameters, componentStatus)
|
||||
if err != nil {
|
||||
if isFatal(err) {
|
||||
return nil, err
|
||||
|
||||
@@ -40,7 +40,7 @@ func evaluateChangesHandler(events []fsnotify.Event, path string, fileIgnores []
|
||||
return changedFiles, deletedPaths
|
||||
}
|
||||
|
||||
func processEventsHandler(changedFiles, deletedPaths []string, _ WatchParameters, out io.Writer, componentStatus *ComponentStatus, backo *ExpBackoff) (*time.Duration, error) {
|
||||
func processEventsHandler(ctx context.Context, changedFiles, deletedPaths []string, _ WatchParameters, out io.Writer, componentStatus *ComponentStatus, backo *ExpBackoff) (*time.Duration, error) {
|
||||
fmt.Fprintf(out, "changedFiles %s deletedPaths %s\n", changedFiles, deletedPaths)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package helper
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -15,6 +16,8 @@ import (
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
"github.com/redhat-developer/odo/pkg/segment"
|
||||
|
||||
@@ -200,11 +203,21 @@ func CommonBeforeEach(setupCluster bool) CommonVar {
|
||||
commonVar.Project = commonVar.CliRunner.CreateAndSetRandNamespaceProject()
|
||||
}
|
||||
commonVar.OriginalWorkingDirectory = Getwd()
|
||||
os.Setenv("GLOBALODOCONFIG", filepath.Join(commonVar.ConfigDir, "preference.yaml"))
|
||||
// Set ConsentTelemetry to false so that it does not prompt to set a preference value
|
||||
cfg, _ := preference.NewClient()
|
||||
err := cfg.SetConfiguration(preference.ConsentTelemetrySetting, "false")
|
||||
|
||||
configPath := filepath.Join(commonVar.ConfigDir, "preference.yaml")
|
||||
os.Setenv("GLOBALODOCONFIG", configPath)
|
||||
|
||||
// Create context with env var configuration
|
||||
ctx := context.Background()
|
||||
envConfig, err := config.GetConfiguration()
|
||||
Expect(err).To(BeNil())
|
||||
ctx = envcontext.WithEnvConfig(ctx, *envConfig)
|
||||
|
||||
// Set ConsentTelemetry to false so that it does not prompt to set a preference value
|
||||
cfg, _ := preference.NewClient(ctx)
|
||||
err = cfg.SetConfiguration(preference.ConsentTelemetrySetting, "false")
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// Use ephemeral volumes (emptyDir) in tests to make test faster
|
||||
err = cfg.SetConfiguration(preference.EphemeralSetting, "true")
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
@@ -13,14 +13,17 @@ import (
|
||||
"github.com/onsi/gomega/types"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/labels"
|
||||
"github.com/redhat-developer/odo/pkg/segment"
|
||||
)
|
||||
|
||||
const (
|
||||
TelemetryCaller = "TELEMETRY_CALLER"
|
||||
)
|
||||
|
||||
func runningCmd(cmd *exec.Cmd) string {
|
||||
prog := filepath.Base(cmd.Path)
|
||||
var env []string
|
||||
for _, e := range cmd.Env {
|
||||
if strings.HasPrefix(e, "ODO_") || e == segment.TelemetryCaller {
|
||||
if strings.HasPrefix(e, "ODO_") || e == TelemetryCaller {
|
||||
env = append(env, e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package helper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -8,20 +9,32 @@ import (
|
||||
_ "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
"github.com/redhat-developer/odo/pkg/segment"
|
||||
)
|
||||
|
||||
const (
|
||||
DebugTelemetryFileEnv = "ODO_DEBUG_TELEMETRY_FILE"
|
||||
)
|
||||
|
||||
func setDebugTelemetryFile(value string) error {
|
||||
return os.Setenv(segment.DebugTelemetryFileEnv, value)
|
||||
return os.Setenv(DebugTelemetryFileEnv, value)
|
||||
}
|
||||
|
||||
// EnableTelemetryDebug creates a temp file to use for debugging telemetry.
|
||||
// it also sets up envs and cfg for the same
|
||||
func EnableTelemetryDebug() {
|
||||
Expect(os.Setenv(segment.TrackingConsentEnv, "yes")).NotTo(HaveOccurred())
|
||||
cfg, _ := preference.NewClient()
|
||||
err := cfg.SetConfiguration(preference.ConsentTelemetrySetting, "true")
|
||||
|
||||
ctx := context.Background()
|
||||
envConfig, err := config.GetConfiguration()
|
||||
Expect(err).To(BeNil())
|
||||
ctx = envcontext.WithEnvConfig(ctx, *envConfig)
|
||||
|
||||
cfg, _ := preference.NewClient(ctx)
|
||||
err = cfg.SetConfiguration(preference.ConsentTelemetrySetting, "true")
|
||||
Expect(err).To(BeNil())
|
||||
tempFile, err := ioutil.TempFile("", "telemetry")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@@ -29,11 +42,15 @@ func EnableTelemetryDebug() {
|
||||
Expect(tempFile.Close()).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func GetDebugTelemetryFile() string {
|
||||
return os.Getenv(DebugTelemetryFileEnv)
|
||||
}
|
||||
|
||||
// GetTelemetryDebugData gets telemetry data dumped into temp file for testing/debugging
|
||||
func GetTelemetryDebugData() segment.TelemetryData {
|
||||
var data []byte
|
||||
var td segment.TelemetryData
|
||||
telemetryFile := segment.GetDebugTelemetryFile()
|
||||
telemetryFile := GetDebugTelemetryFile()
|
||||
Eventually(func() string {
|
||||
d, err := ioutil.ReadFile(telemetryFile)
|
||||
Expect(err).To(BeNil())
|
||||
@@ -48,9 +65,15 @@ func GetTelemetryDebugData() segment.TelemetryData {
|
||||
// ResetTelemetry resets the telemetry back to original values
|
||||
func ResetTelemetry() {
|
||||
Expect(os.Setenv(segment.TrackingConsentEnv, "no")).NotTo(HaveOccurred())
|
||||
Expect(os.Unsetenv(segment.DebugTelemetryFileEnv))
|
||||
cfg, _ := preference.NewClient()
|
||||
err := cfg.SetConfiguration(preference.ConsentTelemetrySetting, "true")
|
||||
Expect(os.Unsetenv(DebugTelemetryFileEnv))
|
||||
|
||||
ctx := context.Background()
|
||||
envConfig, err := config.GetConfiguration()
|
||||
Expect(err).To(BeNil())
|
||||
ctx = envcontext.WithEnvConfig(ctx, *envConfig)
|
||||
|
||||
cfg, _ := preference.NewClient(ctx)
|
||||
err = cfg.SetConfiguration(preference.ConsentTelemetrySetting, "true")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(segment.IsTelemetryEnabled(cfg)).To(BeFalse())
|
||||
Expect(segment.IsTelemetryEnabled(cfg, *envConfig)).To(BeFalse())
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/redhat-developer/odo/pkg/config"
|
||||
envcontext "github.com/redhat-developer/odo/pkg/config/context"
|
||||
"github.com/redhat-developer/odo/pkg/odo/cli/messages"
|
||||
"github.com/redhat-developer/odo/pkg/preference"
|
||||
"github.com/redhat-developer/odo/pkg/segment"
|
||||
@@ -378,23 +381,6 @@ var _ = Describe("odo devfile init command tests", Label(helper.LabelNoCluster),
|
||||
segment.TrackingConsentEnv: "no",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ODO_DISABLE_TELEMETRY=foobar and ODO_TRACKING_CONSENT=no",
|
||||
env: map[string]string{
|
||||
//lint:ignore SA1019 We deprecated this env var, but until it is removed, we still want to test it
|
||||
segment.DisableTelemetryEnv: "foobar-should-evaluate-to-false",
|
||||
segment.TrackingConsentEnv: "no",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ODO_DISABLE_TELEMETRY='' and ODO_TRACKING_CONSENT=no",
|
||||
env: map[string]string{
|
||||
// an empty string will evaluate to false
|
||||
//lint:ignore SA1019 We deprecated this env var, but until it is removed, we still want to test it
|
||||
segment.DisableTelemetryEnv: "",
|
||||
segment.TrackingConsentEnv: "no",
|
||||
},
|
||||
},
|
||||
} {
|
||||
tt := tt
|
||||
It("should error out if "+tt.name, func() {
|
||||
@@ -428,7 +414,7 @@ var _ = Describe("odo devfile init command tests", Label(helper.LabelNoCluster),
|
||||
{
|
||||
title: "empty caller env var",
|
||||
env: map[string]string{
|
||||
segment.TelemetryCaller: "",
|
||||
helper.TelemetryCaller: "",
|
||||
},
|
||||
callerChecker: func(_, _ string, td segment.TelemetryData) {
|
||||
cmdProperties := td.Properties.CmdProperties
|
||||
@@ -439,7 +425,7 @@ var _ = Describe("odo devfile init command tests", Label(helper.LabelNoCluster),
|
||||
{
|
||||
title: "invalid caller env var",
|
||||
env: map[string]string{
|
||||
segment.TelemetryCaller: "an-invalid-caller",
|
||||
helper.TelemetryCaller: "an-invalid-caller",
|
||||
},
|
||||
callerChecker: func(stdout, stderr string, td segment.TelemetryData) {
|
||||
By("not disclosing list of allowed values", func() {
|
||||
@@ -471,7 +457,7 @@ var _ = Describe("odo devfile init command tests", Label(helper.LabelNoCluster),
|
||||
telemetryTests = append(telemetryTests, telemetryTest{
|
||||
title: fmt.Sprintf("valid caller env var: %s", c),
|
||||
env: map[string]string{
|
||||
segment.TelemetryCaller: c,
|
||||
helper.TelemetryCaller: c,
|
||||
},
|
||||
callerChecker: func(_, _ string, td segment.TelemetryData) {
|
||||
Expect(td.Properties.CmdProperties[segmentContext.Caller]).To(Equal(c))
|
||||
@@ -486,7 +472,12 @@ var _ = Describe("odo devfile init command tests", Label(helper.LabelNoCluster),
|
||||
BeforeEach(func() {
|
||||
helper.EnableTelemetryDebug()
|
||||
|
||||
cfg, err := preference.NewClient()
|
||||
ctx := context.Background()
|
||||
envConfig, err := config.GetConfiguration()
|
||||
Expect(err).To(BeNil())
|
||||
ctx = envcontext.WithEnvConfig(ctx, *envConfig)
|
||||
|
||||
cfg, err := preference.NewClient(ctx)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
if tt.setupFunc != nil {
|
||||
tt.setupFunc(cfg)
|
||||
|
||||
@@ -134,7 +134,7 @@ var _ = Describe("odo generic", func() {
|
||||
})
|
||||
|
||||
Context("experimental mode has an unknown value", func() {
|
||||
for _, val := range []string{"", "false", "foobar"} {
|
||||
for _, val := range []string{"", "false"} {
|
||||
val := val
|
||||
It("should not list experimental flags if ODO_EXPERIMENTAL is not true", func() {
|
||||
helpOutput := helper.Cmd("odo", "help").AddEnv(feature.OdoExperimentalModeEnvVar + "=" + val).ShouldPass().Out()
|
||||
|
||||
8
vendor/github.com/sethvargo/go-envconfig/AUTHORS
generated
vendored
Normal file
8
vendor/github.com/sethvargo/go-envconfig/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# This is the list of envconfig authors for copyright purposes.
|
||||
#
|
||||
# This does not necessarily list everyone who has contributed code, since in
|
||||
# some cases, their employer may be the copyright holder. To see the full list
|
||||
# of contributors, see the revision history in source control.
|
||||
|
||||
Google LLC
|
||||
Seth Vargo
|
||||
202
vendor/github.com/sethvargo/go-envconfig/LICENSE
generated
vendored
Normal file
202
vendor/github.com/sethvargo/go-envconfig/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed 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.
|
||||
22
vendor/github.com/sethvargo/go-envconfig/Makefile
generated
vendored
Normal file
22
vendor/github.com/sethvargo/go-envconfig/Makefile
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Copyright The envconfig Authors
|
||||
#
|
||||
# Licensed 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.
|
||||
|
||||
test:
|
||||
@go test \
|
||||
-count=1 \
|
||||
-race \
|
||||
-shuffle=on \
|
||||
-timeout=10m \
|
||||
./...
|
||||
.PHONY: test
|
||||
414
vendor/github.com/sethvargo/go-envconfig/README.md
generated
vendored
Normal file
414
vendor/github.com/sethvargo/go-envconfig/README.md
generated
vendored
Normal file
@@ -0,0 +1,414 @@
|
||||
# Envconfig
|
||||
|
||||
[](https://pkg.go.dev/mod/github.com/sethvargo/go-envconfig)
|
||||
[](https://github.com/sethvargo/go-envconfig/actions?query=branch%3Amain+-event%3Aschedule)
|
||||
|
||||
Envconfig populates struct field values based on environment variables or
|
||||
arbitrary lookup functions. It supports pre-setting mutations, which is useful
|
||||
for things like converting values to uppercase, trimming whitespace, or looking
|
||||
up secrets.
|
||||
|
||||
**Note:** Versions prior to v0.2 used a different import path. This README and
|
||||
examples are for v0.2+.
|
||||
|
||||
## Usage
|
||||
|
||||
Define a struct with fields using the `env` tag:
|
||||
|
||||
```go
|
||||
type MyConfig struct {
|
||||
Port int `env:"PORT"`
|
||||
Username string `env:"USERNAME"`
|
||||
}
|
||||
```
|
||||
|
||||
Set some environment variables:
|
||||
|
||||
```sh
|
||||
export PORT=5555
|
||||
export USERNAME=yoyo
|
||||
```
|
||||
|
||||
Process it using envconfig:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/sethvargo/go-envconfig"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
var c MyConfig
|
||||
if err := envconfig.Process(ctx, &c); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// c.Port = 5555
|
||||
// c.Username = "yoyo"
|
||||
}
|
||||
```
|
||||
|
||||
You can also use nested structs, just remember that any fields you want to
|
||||
process must be public:
|
||||
|
||||
```go
|
||||
type MyConfig struct {
|
||||
Database *DatabaseConfig
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Port int `env:"PORT"`
|
||||
Username string `env:"USERNAME"`
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Use the `env` struct tag to define configuration.
|
||||
|
||||
### Required
|
||||
|
||||
If a field is required, processing will error if the environment variable is
|
||||
unset.
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
Port int `env:"PORT,required"`
|
||||
}
|
||||
```
|
||||
|
||||
It is invalid to have a field as both `required` and `default`.
|
||||
|
||||
### Default
|
||||
|
||||
If an environment variable is not set, the field will be set to the default
|
||||
value. Note that the environment variable must not be set (e.g. `unset PORT`).
|
||||
If the environment variable is the empty string, that counts as a "value" and
|
||||
the default will not be used.
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
Port int `env:"PORT,default=5555"`
|
||||
}
|
||||
```
|
||||
|
||||
You can also set the default value to another field or value from the
|
||||
environment, for example:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
DefaultPort int `env:"DEFAULT_PORT,default=5555"`
|
||||
Port int `env:"OVERRIDE_PORT,default=$DEFAULT_PORT"`
|
||||
}
|
||||
```
|
||||
|
||||
The value for `Port` will default to the value of `DEFAULT_PORT`.
|
||||
|
||||
It is invalid to have a field as both `required` and `default`.
|
||||
|
||||
### Prefix
|
||||
|
||||
For shared, embedded structs, you can define a prefix to use when processing
|
||||
struct values for that embed.
|
||||
|
||||
```go
|
||||
type SharedConfig struct {
|
||||
Port int `env:"PORT,default=5555"`
|
||||
}
|
||||
|
||||
type Server1 struct {
|
||||
// This processes Port from $FOO_PORT.
|
||||
*SharedConfig `env:",prefix=FOO_"`
|
||||
}
|
||||
|
||||
type Server2 struct {
|
||||
// This processes Port from $BAR_PORT.
|
||||
*SharedConfig `env:",prefix=BAR_"`
|
||||
}
|
||||
```
|
||||
|
||||
It is invalid to specify a prefix on non-struct fields.
|
||||
|
||||
### Overwrite
|
||||
|
||||
If overwrite is set, the value will be overwritten if there is an environment
|
||||
variable match regardless if the value is non-zero.
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
Port int `env:"PORT,overwrite"`
|
||||
}
|
||||
```
|
||||
|
||||
The rules for overwrite + default are:
|
||||
|
||||
- If the struct field has the zero value and a default is set:
|
||||
|
||||
- If no environment variable is specified, the struct field will be
|
||||
populated with the default value.
|
||||
|
||||
- If an environment variable is specified, the struct field will be
|
||||
populate with the environment variable value.
|
||||
|
||||
- If the struct field has a non-zero value and a default is set:
|
||||
|
||||
- If no environment variable is specified, the struct field's existing
|
||||
value will be used (the default is ignored).
|
||||
|
||||
- If an environment variable is specified, the struct field's existing
|
||||
value will be overwritten with the environment variable value.
|
||||
|
||||
|
||||
## Complex Types
|
||||
|
||||
**Note:** Complex types are only decoded or unmarshalled when the environment
|
||||
variable is defined or a default is specified. The decoding/unmarshalling
|
||||
functions are _not_ invoked when a value is not defined.
|
||||
|
||||
### Durations
|
||||
|
||||
In the environment, `time.Duration` values are specified as a parsable Go
|
||||
duration:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
MyVar time.Duration `env:"MYVAR"`
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
export MYVAR="10m" # 10 * time.Minute
|
||||
```
|
||||
|
||||
### TextUnmarshaler / BinaryUnmarshaler
|
||||
|
||||
Types that implement `TextUnmarshaler` or `BinaryUnmarshaler` are processed as such.
|
||||
|
||||
### json.Unmarshaler
|
||||
|
||||
Types that implement `json.Unmarshaler` are processed as such.
|
||||
|
||||
### gob.Decoder
|
||||
|
||||
Types that implement `gob.Decoder` are processed as such.
|
||||
|
||||
|
||||
### Slices
|
||||
|
||||
Slices are specified as comma-separated values:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
MyVar []string `env:"MYVAR"`
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
export MYVAR="a,b,c,d" # []string{"a", "b", "c", "d"}
|
||||
```
|
||||
|
||||
Define a custom delimiter with `delimiter`:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
MyVar []string `env:"MYVAR,delimiter=;"`
|
||||
```
|
||||
|
||||
```bash
|
||||
export MYVAR="a;b;c;d" # []string{"a", "b", "c", "d"}
|
||||
```
|
||||
|
||||
Note that byte slices are special cased and interpreted as strings from the
|
||||
environment.
|
||||
|
||||
### Maps
|
||||
|
||||
Maps are specified as comma-separated key:value pairs:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
MyVar map[string]string `env:"MYVAR"`
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
export MYVAR="a:b,c:d" # map[string]string{"a":"b", "c":"d"}
|
||||
```
|
||||
|
||||
Define a custom delimiter with `delimiter`:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
MyVar map[string]string `env:"MYVAR,delimiter=;"`
|
||||
```
|
||||
|
||||
```bash
|
||||
export MYVAR="a:b;c:d" # map[string]string{"a":"b", "c":"d"}
|
||||
```
|
||||
|
||||
Define a separator with `separator`:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
MyVar map[string]string `env:"MYVAR,separator=|"`
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
export MYVAR="a|b,c|d" # map[string]string{"a":"b", "c":"d"}
|
||||
```
|
||||
|
||||
|
||||
### Structs
|
||||
|
||||
Envconfig walks the entire struct, including nested structs, so deeply-nested
|
||||
fields are also supported.
|
||||
|
||||
If a nested struct is a pointer type, it will automatically be instantianted to
|
||||
the non-nil value. To change this behavior, see
|
||||
[Initialization](#Initialization).
|
||||
|
||||
|
||||
### Custom
|
||||
|
||||
You can also [define your own decoder](#Extension).
|
||||
|
||||
|
||||
## Prefixing
|
||||
|
||||
You can define a custom prefix using the `PrefixLookuper`. This will lookup
|
||||
values in the environment by prefixing the keys with the provided value:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
MyVar string `env:"MYVAR"`
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// Process variables, but look for the "APP_" prefix.
|
||||
l := envconfig.PrefixLookuper("APP_", envconfig.OsLookuper())
|
||||
if err := envconfig.ProcessWith(ctx, &c, l); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
export APP_MYVAR="foo"
|
||||
```
|
||||
|
||||
## Initialization
|
||||
|
||||
By default, all pointer fields are initialized (allocated) so they are not
|
||||
`nil`. To disable this behavior, use the tag the field as `noinit`:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
// Without `noinit`, DeleteUser would be initialized to the default boolean
|
||||
// value. With `noinit`, if the environment variable is not given, the value
|
||||
// is kept as uninitialized (nil).
|
||||
DeleteUser *bool `env:"DELETE_USER, noinit"`
|
||||
}
|
||||
```
|
||||
|
||||
This also applies to nested fields in a struct:
|
||||
|
||||
```go
|
||||
type ParentConfig struct {
|
||||
// Without `noinit` tag, `Child` would be set to `&ChildConfig{}` whether
|
||||
// or not `FIELD` is set in the env var.
|
||||
// With `noinit`, `Child` would stay nil if `FIELD` is not set in the env var.
|
||||
Child *ChildConfig `env:",noinit"`
|
||||
}
|
||||
|
||||
type ChildConfig struct {
|
||||
Field string `env:"FIELD"`
|
||||
}
|
||||
```
|
||||
|
||||
The `noinit` tag is only applicable for pointer fields. Putting the tag on a
|
||||
non-struct-pointer will return an error.
|
||||
|
||||
|
||||
## Extension
|
||||
|
||||
All built-in types are supported except `Func` and `Chan`. If you need to define
|
||||
a custom decoder, implement the `Decoder` interface:
|
||||
|
||||
```go
|
||||
type MyStruct struct {
|
||||
field string
|
||||
}
|
||||
|
||||
func (v *MyStruct) EnvDecode(val string) error {
|
||||
v.field = fmt.Sprintf("PREFIX-%s", val)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
If you need to modify environment variable values before processing, you can
|
||||
specify a custom `Mutator`:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Password `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func resolveSecretFunc(ctx context.Context, key, value string) (string, error) {
|
||||
if strings.HasPrefix(value, "secret://") {
|
||||
return secretmanager.Resolve(ctx, value) // example
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
var config Config
|
||||
envconfig.ProcessWith(ctx, &config, envconfig.OsLookuper(), resolveSecretFunc)
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Relying on the environment in tests can be troublesome because environment
|
||||
variables are global, which makes it difficult to parallelize the tests.
|
||||
Envconfig supports extracting data from anything that returns a value:
|
||||
|
||||
```go
|
||||
lookuper := envconfig.MapLookuper(map[string]string{
|
||||
"FOO": "bar",
|
||||
"ZIP": "zap",
|
||||
})
|
||||
|
||||
var config Config
|
||||
envconfig.ProcessWith(ctx, &config, lookuper)
|
||||
```
|
||||
|
||||
Now you can parallelize all your tests by providing a map for the lookup
|
||||
function. In fact, that's how the tests in this repo work, so check there for an
|
||||
example.
|
||||
|
||||
You can also combine multiple lookupers with `MultiLookuper`. See the GoDoc for
|
||||
more information and examples.
|
||||
|
||||
|
||||
## Inspiration
|
||||
|
||||
This library is conceptually similar to [kelseyhightower/envconfig](https://github.com/kelseyhightower/envconfig), with the following
|
||||
major behavioral differences:
|
||||
|
||||
- Adds support for specifying a custom lookup function (such as a map), which
|
||||
is useful for testing.
|
||||
|
||||
- Only populates fields if they contain zero or nil values if `overwrite` is
|
||||
unset. This means you can pre-initialize a struct and any pre-populated
|
||||
fields will not be overwritten during processing.
|
||||
|
||||
- Support for interpolation. The default value for a field can be the value of
|
||||
another field.
|
||||
|
||||
- Support for arbitrary mutators that change/resolve data before type
|
||||
conversion.
|
||||
57
vendor/github.com/sethvargo/go-envconfig/decoding.go
generated
vendored
Normal file
57
vendor/github.com/sethvargo/go-envconfig/decoding.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright The envconfig Authors
|
||||
//
|
||||
// Licensed 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 envconfig
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Base64Bytes is a slice of bytes where the information is base64-encoded in
|
||||
// the environment variable.
|
||||
type Base64Bytes []byte
|
||||
|
||||
// EnvDecode implements env.Decoder.
|
||||
func (b *Base64Bytes) EnvDecode(val string) error {
|
||||
val = strings.ReplaceAll(val, "+", "-")
|
||||
val = strings.ReplaceAll(val, "/", "_")
|
||||
val = strings.TrimRight(val, "=")
|
||||
|
||||
var err error
|
||||
*b, err = base64.RawURLEncoding.DecodeString(val)
|
||||
return err
|
||||
}
|
||||
|
||||
// Bytes returns the underlying bytes.
|
||||
func (b Base64Bytes) Bytes() []byte {
|
||||
return []byte(b)
|
||||
}
|
||||
|
||||
// HexBytes is a slice of bytes where the information is hex-encoded in the
|
||||
// environment variable.
|
||||
type HexBytes []byte
|
||||
|
||||
// EnvDecode implements env.Decoder.
|
||||
func (b *HexBytes) EnvDecode(val string) error {
|
||||
var err error
|
||||
*b, err = hex.DecodeString(val)
|
||||
return err
|
||||
}
|
||||
|
||||
// Bytes returns the underlying bytes.
|
||||
func (b HexBytes) Bytes() []byte {
|
||||
return []byte(b)
|
||||
}
|
||||
717
vendor/github.com/sethvargo/go-envconfig/envconfig.go
generated
vendored
Normal file
717
vendor/github.com/sethvargo/go-envconfig/envconfig.go
generated
vendored
Normal file
@@ -0,0 +1,717 @@
|
||||
// Copyright The envconfig Authors
|
||||
//
|
||||
// Licensed 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 envconfig populates struct fields based on environment variable
|
||||
// values (or anything that responds to "Lookup"). Structs declare their
|
||||
// environment dependencies using the "env" tag with the key being the name of
|
||||
// the environment variable, case sensitive.
|
||||
//
|
||||
// type MyStruct struct {
|
||||
// A string `env:"A"` // resolves A to $A
|
||||
// B string `env:"B,required"` // resolves B to $B, errors if $B is unset
|
||||
// C string `env:"C,default=foo"` // resolves C to $C, defaults to "foo"
|
||||
//
|
||||
// D string `env:"D,required,default=foo"` // error, cannot be required and default
|
||||
// E string `env:""` // error, must specify key
|
||||
// }
|
||||
//
|
||||
// All built-in types are supported except Func and Chan. If you need to define
|
||||
// a custom decoder, implement Decoder:
|
||||
//
|
||||
// type MyStruct struct {
|
||||
// field string
|
||||
// }
|
||||
//
|
||||
// func (v *MyStruct) EnvDecode(val string) error {
|
||||
// v.field = fmt.Sprintf("PREFIX-%s", val)
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// In the environment, slices are specified as comma-separated values:
|
||||
//
|
||||
// export MYVAR="a,b,c,d" // []string{"a", "b", "c", "d"}
|
||||
//
|
||||
// In the environment, maps are specified as comma-separated key:value pairs:
|
||||
//
|
||||
// export MYVAR="a:b,c:d" // map[string]string{"a":"b", "c":"d"}
|
||||
//
|
||||
// If you need to modify environment variable values before processing, you can
|
||||
// specify a custom mutator:
|
||||
//
|
||||
// type Config struct {
|
||||
// Password `env:"PASSWORD_SECRET"`
|
||||
// }
|
||||
//
|
||||
// func resolveSecretFunc(ctx context.Context, key, value string) (string, error) {
|
||||
// if strings.HasPrefix(value, "secret://") {
|
||||
// return secretmanager.Resolve(ctx, value) // example
|
||||
// }
|
||||
// return value, nil
|
||||
// }
|
||||
//
|
||||
// var config Config
|
||||
// ProcessWith(&config, OsLookuper(), resolveSecretFunc)
|
||||
package envconfig
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
envTag = "env"
|
||||
|
||||
optDefault = "default="
|
||||
optDelimiter = "delimiter="
|
||||
optNoInit = "noinit"
|
||||
optOverwrite = "overwrite"
|
||||
optPrefix = "prefix="
|
||||
optRequired = "required"
|
||||
optSeparator = "separator="
|
||||
|
||||
defaultDelimiter = ","
|
||||
defaultSeparator = ":"
|
||||
)
|
||||
|
||||
// Error is a custom error type for errors returned by envconfig.
|
||||
type Error string
|
||||
|
||||
// Error implements error.
|
||||
func (e Error) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
const (
|
||||
ErrInvalidEnvvarName = Error("invalid environment variable name")
|
||||
ErrInvalidMapItem = Error("invalid map item")
|
||||
ErrLookuperNil = Error("lookuper cannot be nil")
|
||||
ErrMissingKey = Error("missing key")
|
||||
ErrMissingRequired = Error("missing required value")
|
||||
ErrNoInitNotPtr = Error("field must be a pointer to have noinit")
|
||||
ErrNotPtr = Error("input must be a pointer")
|
||||
ErrNotStruct = Error("input must be a struct")
|
||||
ErrPrefixNotStruct = Error("prefix is only valid on struct types")
|
||||
ErrPrivateField = Error("cannot parse private fields")
|
||||
ErrRequiredAndDefault = Error("field cannot be required and have a default value")
|
||||
ErrUnknownOption = Error("unknown option")
|
||||
)
|
||||
|
||||
// Lookuper is an interface that provides a lookup for a string-based key.
|
||||
type Lookuper interface {
|
||||
// Lookup searches for the given key and returns the corresponding string
|
||||
// value. If a value is found, it returns the value and true. If a value is
|
||||
// not found, it returns the empty string and false.
|
||||
Lookup(key string) (string, bool)
|
||||
}
|
||||
|
||||
// osLookuper looks up environment configuration from the local environment.
|
||||
type osLookuper struct{}
|
||||
|
||||
// Verify implements interface.
|
||||
var _ Lookuper = (*osLookuper)(nil)
|
||||
|
||||
func (o *osLookuper) Lookup(key string) (string, bool) {
|
||||
return os.LookupEnv(key)
|
||||
}
|
||||
|
||||
// OsLookuper returns a lookuper that uses the environment ([os.LookupEnv]) to
|
||||
// resolve values.
|
||||
func OsLookuper() Lookuper {
|
||||
return new(osLookuper)
|
||||
}
|
||||
|
||||
type mapLookuper map[string]string
|
||||
|
||||
var _ Lookuper = (*mapLookuper)(nil)
|
||||
|
||||
func (m mapLookuper) Lookup(key string) (string, bool) {
|
||||
v, ok := m[key]
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// MapLookuper looks up environment configuration from a provided map. This is
|
||||
// useful for testing, especially in parallel, since it does not require you to
|
||||
// mutate the parent environment (which is stateful).
|
||||
func MapLookuper(m map[string]string) Lookuper {
|
||||
return mapLookuper(m)
|
||||
}
|
||||
|
||||
type multiLookuper struct {
|
||||
ls []Lookuper
|
||||
}
|
||||
|
||||
var _ Lookuper = (*multiLookuper)(nil)
|
||||
|
||||
func (m *multiLookuper) Lookup(key string) (string, bool) {
|
||||
for _, l := range m.ls {
|
||||
if v, ok := l.Lookup(key); ok {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// PrefixLookuper looks up environment configuration using the specified prefix.
|
||||
// This is useful if you want all your variables to start with a particular
|
||||
// prefix like "MY_APP_".
|
||||
func PrefixLookuper(prefix string, l Lookuper) Lookuper {
|
||||
if typ, ok := l.(*prefixLookuper); ok {
|
||||
return &prefixLookuper{prefix: typ.prefix + prefix, l: typ.l}
|
||||
}
|
||||
return &prefixLookuper{prefix: prefix, l: l}
|
||||
}
|
||||
|
||||
type prefixLookuper struct {
|
||||
l Lookuper
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (p *prefixLookuper) Lookup(key string) (string, bool) {
|
||||
return p.l.Lookup(p.prefix + key)
|
||||
}
|
||||
|
||||
// MultiLookuper wraps a collection of lookupers. It does not combine them, and
|
||||
// lookups appear in the order in which they are provided to the initializer.
|
||||
func MultiLookuper(lookupers ...Lookuper) Lookuper {
|
||||
return &multiLookuper{ls: lookupers}
|
||||
}
|
||||
|
||||
// Decoder is an interface that custom types/fields can implement to control how
|
||||
// decoding takes place. For example:
|
||||
//
|
||||
// type MyType string
|
||||
//
|
||||
// func (mt MyType) EnvDecode(val string) error {
|
||||
// return "CUSTOM-"+val
|
||||
// }
|
||||
type Decoder interface {
|
||||
EnvDecode(val string) error
|
||||
}
|
||||
|
||||
// MutatorFunc is a function that mutates a given value before it is passed
|
||||
// along for processing. This is useful if you want to mutate the environment
|
||||
// variable value before it's converted to the proper type.
|
||||
type MutatorFunc func(ctx context.Context, k, v string) (string, error)
|
||||
|
||||
// options are internal options for decoding.
|
||||
type options struct {
|
||||
Default string
|
||||
Delimiter string
|
||||
Prefix string
|
||||
Separator string
|
||||
NoInit bool
|
||||
Overwrite bool
|
||||
Required bool
|
||||
}
|
||||
|
||||
// Process processes the struct using the environment. See [ProcessWith] for a
|
||||
// more customizable version.
|
||||
func Process(ctx context.Context, i interface{}) error {
|
||||
return ProcessWith(ctx, i, OsLookuper())
|
||||
}
|
||||
|
||||
// ProcessWith processes the given interface with the given lookuper. See the
|
||||
// package-level documentation for specific examples and behaviors.
|
||||
func ProcessWith(ctx context.Context, i interface{}, l Lookuper, fns ...MutatorFunc) error {
|
||||
return processWith(ctx, i, l, false, fns...)
|
||||
}
|
||||
|
||||
// processWith is a helper that captures whether the parent wanted
|
||||
// initialization.
|
||||
func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bool, fns ...MutatorFunc) error {
|
||||
if l == nil {
|
||||
return ErrLookuperNil
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(i)
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return ErrNotPtr
|
||||
}
|
||||
|
||||
e := v.Elem()
|
||||
if e.Kind() != reflect.Struct {
|
||||
return ErrNotStruct
|
||||
}
|
||||
|
||||
t := e.Type()
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
ef := e.Field(i)
|
||||
tf := t.Field(i)
|
||||
tag := tf.Tag.Get(envTag)
|
||||
|
||||
if !ef.CanSet() {
|
||||
if tag != "" {
|
||||
// There's an "env" tag on a private field, we can't alter it, and it's
|
||||
// likely a mistake. Return an error so the user can handle.
|
||||
return fmt.Errorf("%s: %w", tf.Name, ErrPrivateField)
|
||||
}
|
||||
|
||||
// Otherwise continue to the next field.
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse the key and options.
|
||||
key, opts, err := keyAndOpts(tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", tf.Name, err)
|
||||
}
|
||||
|
||||
// NoInit is only permitted on pointers.
|
||||
if opts.NoInit && ef.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("%s: %w", tf.Name, ErrNoInitNotPtr)
|
||||
}
|
||||
shouldNotInit := opts.NoInit || parentNoInit
|
||||
|
||||
isNilStructPtr := false
|
||||
setNilStruct := func(v reflect.Value) {
|
||||
origin := e.Field(i)
|
||||
if isNilStructPtr {
|
||||
empty := reflect.New(origin.Type().Elem()).Interface()
|
||||
|
||||
// If a struct (after traversal) equals to the empty value, it means
|
||||
// nothing was changed in any sub-fields. With the noinit opt, we skip
|
||||
// setting the empty value to the original struct pointer (keep it nil).
|
||||
if !reflect.DeepEqual(v.Interface(), empty) || !shouldNotInit {
|
||||
origin.Set(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize pointer structs.
|
||||
pointerWasSet := false
|
||||
for ef.Kind() == reflect.Ptr {
|
||||
if ef.IsNil() {
|
||||
if ef.Type().Elem().Kind() != reflect.Struct {
|
||||
// This is a nil pointer to something that isn't a struct, like
|
||||
// *string. Move along.
|
||||
break
|
||||
}
|
||||
|
||||
isNilStructPtr = true
|
||||
// Use an empty struct of the type so we can traverse.
|
||||
ef = reflect.New(ef.Type().Elem()).Elem()
|
||||
} else {
|
||||
pointerWasSet = true
|
||||
ef = ef.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Special case handle structs. This has to come after the value resolution in
|
||||
// case the struct has a custom decoder.
|
||||
if ef.Kind() == reflect.Struct {
|
||||
for ef.CanAddr() {
|
||||
ef = ef.Addr()
|
||||
}
|
||||
|
||||
// Lookup the value, ignoring an error if the key isn't defined. This is
|
||||
// required for nested structs that don't declare their own `env` keys,
|
||||
// but have internal fields with an `env` defined.
|
||||
val, _, _, err := lookup(key, opts, l)
|
||||
if err != nil && !errors.Is(err, ErrMissingKey) {
|
||||
return fmt.Errorf("%s: %w", tf.Name, err)
|
||||
}
|
||||
|
||||
if ok, err := processAsDecoder(val, ef); ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
setNilStruct(ef)
|
||||
continue
|
||||
}
|
||||
|
||||
plu := l
|
||||
if opts.Prefix != "" {
|
||||
plu = PrefixLookuper(opts.Prefix, l)
|
||||
}
|
||||
|
||||
if err := processWith(ctx, ef.Interface(), plu, shouldNotInit, fns...); err != nil {
|
||||
return fmt.Errorf("%s: %w", tf.Name, err)
|
||||
}
|
||||
|
||||
setNilStruct(ef)
|
||||
continue
|
||||
}
|
||||
|
||||
// It's invalid to have a prefix on a non-struct field.
|
||||
if opts.Prefix != "" {
|
||||
return ErrPrefixNotStruct
|
||||
}
|
||||
|
||||
// Stop processing if there's no env tag (this comes after nested parsing),
|
||||
// in case there's an env tag in an embedded struct.
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// The field already has a non-zero value and overwrite is false, do not
|
||||
// overwrite.
|
||||
if (pointerWasSet || !ef.IsZero()) && !opts.Overwrite {
|
||||
continue
|
||||
}
|
||||
|
||||
val, found, usedDefault, err := lookup(key, opts, l)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", tf.Name, err)
|
||||
}
|
||||
|
||||
// If the field already has a non-zero value and there was no value directly
|
||||
// specified, do not overwrite the existing field. We only want to overwrite
|
||||
// when the envvar was provided directly.
|
||||
if (pointerWasSet || !ef.IsZero()) && !found {
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply any mutators. Mutators are applied after the lookup, but before any
|
||||
// type conversions. They always resolve to a string (or error), so we don't
|
||||
// call mutators when the environment variable was not set.
|
||||
if found || usedDefault {
|
||||
for _, fn := range fns {
|
||||
if fn != nil {
|
||||
val, err = fn(ctx, key, val)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", tf.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If Delimiter is not defined set it to ","
|
||||
if opts.Delimiter == "" {
|
||||
opts.Delimiter = defaultDelimiter
|
||||
}
|
||||
|
||||
// If Separator is not defined set it to ":"
|
||||
if opts.Separator == "" {
|
||||
opts.Separator = defaultSeparator
|
||||
}
|
||||
|
||||
// Set value.
|
||||
if err := processField(val, ef, opts.Delimiter, opts.Separator, opts.NoInit); err != nil {
|
||||
return fmt.Errorf("%s(%q): %w", tf.Name, val, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// keyAndOpts parses the given tag value (e.g. env:"foo,required") and
|
||||
// returns the key name and options as a list.
|
||||
func keyAndOpts(tag string) (string, *options, error) {
|
||||
parts := strings.Split(tag, ",")
|
||||
key, tagOpts := strings.TrimSpace(parts[0]), parts[1:]
|
||||
|
||||
if key != "" && !validateEnvName(key) {
|
||||
return "", nil, fmt.Errorf("%q: %w ", key, ErrInvalidEnvvarName)
|
||||
}
|
||||
|
||||
var opts options
|
||||
|
||||
LOOP:
|
||||
for i, o := range tagOpts {
|
||||
o = strings.TrimSpace(o)
|
||||
switch {
|
||||
case o == optOverwrite:
|
||||
opts.Overwrite = true
|
||||
case o == optRequired:
|
||||
opts.Required = true
|
||||
case o == optNoInit:
|
||||
opts.NoInit = true
|
||||
case strings.HasPrefix(o, optPrefix):
|
||||
opts.Prefix = strings.TrimPrefix(o, optPrefix)
|
||||
case strings.HasPrefix(o, optDelimiter):
|
||||
opts.Delimiter = strings.TrimPrefix(o, optDelimiter)
|
||||
case strings.HasPrefix(o, optSeparator):
|
||||
opts.Separator = strings.TrimPrefix(o, optSeparator)
|
||||
case strings.HasPrefix(o, optDefault):
|
||||
// If a default value was given, assume everything after is the provided
|
||||
// value, including comma-seprated items.
|
||||
o = strings.TrimLeft(strings.Join(tagOpts[i:], ","), " ")
|
||||
opts.Default = strings.TrimPrefix(o, optDefault)
|
||||
break LOOP
|
||||
default:
|
||||
return "", nil, fmt.Errorf("%q: %w", o, ErrUnknownOption)
|
||||
}
|
||||
}
|
||||
|
||||
return key, &opts, nil
|
||||
}
|
||||
|
||||
// lookup looks up the given key using the provided Lookuper and options. The
|
||||
// first boolean parameter indicates whether the value was found in the
|
||||
// lookuper. The second boolean parameter indicates whether the default value
|
||||
// was used.
|
||||
func lookup(key string, opts *options, l Lookuper) (string, bool, bool, error) {
|
||||
if key == "" {
|
||||
// The struct has something like `env:",required"`, which is likely a
|
||||
// mistake. We could try to infer the envvar from the field name, but that
|
||||
// feels too magical.
|
||||
return "", false, false, ErrMissingKey
|
||||
}
|
||||
|
||||
if opts.Required && opts.Default != "" {
|
||||
// Having a default value on a required value doesn't make sense.
|
||||
return "", false, false, ErrRequiredAndDefault
|
||||
}
|
||||
|
||||
// Lookup value.
|
||||
val, found := l.Lookup(key)
|
||||
if !found {
|
||||
if opts.Required {
|
||||
if pl, ok := l.(*prefixLookuper); ok {
|
||||
key = pl.prefix + key
|
||||
}
|
||||
|
||||
return "", false, false, fmt.Errorf("%w: %s", ErrMissingRequired, key)
|
||||
}
|
||||
|
||||
if opts.Default != "" {
|
||||
// Expand the default value. This allows for a default value that maps to
|
||||
// a different variable.
|
||||
val = os.Expand(opts.Default, func(i string) string {
|
||||
s, ok := l.Lookup(i)
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
})
|
||||
|
||||
return val, false, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return val, found, false, nil
|
||||
}
|
||||
|
||||
// processAsDecoder processes the given value as a decoder or custom
|
||||
// unmarshaller.
|
||||
func processAsDecoder(v string, ef reflect.Value) (bool, error) {
|
||||
// Keep a running error. It's possible that a property might implement
|
||||
// multiple decoders, and we don't know *which* decoder will succeed. If we
|
||||
// get through all of them, we'll return the most recent error.
|
||||
var imp bool
|
||||
var err error
|
||||
|
||||
// Resolve any pointers.
|
||||
for ef.CanAddr() {
|
||||
ef = ef.Addr()
|
||||
}
|
||||
|
||||
if ef.CanInterface() {
|
||||
iface := ef.Interface()
|
||||
|
||||
// If a developer chooses to implement the Decoder interface on a type,
|
||||
// never attempt to use other decoders in case of failure. EnvDecode's
|
||||
// decoding logic is "the right one", and the error returned (if any)
|
||||
// is the most specific we can get.
|
||||
if dec, ok := iface.(Decoder); ok {
|
||||
imp = true
|
||||
err = dec.EnvDecode(v)
|
||||
return imp, err
|
||||
}
|
||||
|
||||
if tu, ok := iface.(encoding.TextUnmarshaler); ok {
|
||||
imp = true
|
||||
if err = tu.UnmarshalText([]byte(v)); err == nil {
|
||||
return imp, nil
|
||||
}
|
||||
}
|
||||
|
||||
if tu, ok := iface.(json.Unmarshaler); ok {
|
||||
imp = true
|
||||
if err = tu.UnmarshalJSON([]byte(v)); err == nil {
|
||||
return imp, nil
|
||||
}
|
||||
}
|
||||
|
||||
if tu, ok := iface.(encoding.BinaryUnmarshaler); ok {
|
||||
imp = true
|
||||
if err = tu.UnmarshalBinary([]byte(v)); err == nil {
|
||||
return imp, nil
|
||||
}
|
||||
}
|
||||
|
||||
if tu, ok := iface.(gob.GobDecoder); ok {
|
||||
imp = true
|
||||
if err = tu.GobDecode([]byte(v)); err == nil {
|
||||
return imp, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imp, err
|
||||
}
|
||||
|
||||
func processField(v string, ef reflect.Value, delimiter, separator string, noInit bool) error {
|
||||
// If the input value is empty and initialization is skipped, do nothing.
|
||||
if v == "" && noInit {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle pointers and uninitialized pointers.
|
||||
for ef.Type().Kind() == reflect.Ptr {
|
||||
if ef.IsNil() {
|
||||
ef.Set(reflect.New(ef.Type().Elem()))
|
||||
}
|
||||
ef = ef.Elem()
|
||||
}
|
||||
|
||||
tf := ef.Type()
|
||||
tk := tf.Kind()
|
||||
|
||||
// Handle existing decoders.
|
||||
if ok, err := processAsDecoder(v, ef); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// We don't check if the value is empty earlier, because the user might want
|
||||
// to define a custom decoder and treat the empty variable as a special case.
|
||||
// However, if we got this far, none of the remaining parsers will succeed, so
|
||||
// bail out now.
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch tk {
|
||||
case reflect.Bool:
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ef.SetBool(b)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
f, err := strconv.ParseFloat(v, tf.Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ef.SetFloat(f)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
|
||||
i, err := strconv.ParseInt(v, 0, tf.Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ef.SetInt(i)
|
||||
case reflect.Int64:
|
||||
// Special case time.Duration values.
|
||||
if tf.PkgPath() == "time" && tf.Name() == "Duration" {
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ef.SetInt(int64(d))
|
||||
} else {
|
||||
i, err := strconv.ParseInt(v, 0, tf.Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ef.SetInt(i)
|
||||
}
|
||||
case reflect.String:
|
||||
ef.SetString(v)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
i, err := strconv.ParseUint(v, 0, tf.Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ef.SetUint(i)
|
||||
|
||||
case reflect.Interface:
|
||||
return fmt.Errorf("cannot decode into interfaces")
|
||||
|
||||
// Maps
|
||||
case reflect.Map:
|
||||
vals := strings.Split(v, delimiter)
|
||||
mp := reflect.MakeMapWithSize(tf, len(vals))
|
||||
for _, val := range vals {
|
||||
pair := strings.SplitN(val, separator, 2)
|
||||
if len(pair) < 2 {
|
||||
return fmt.Errorf("%s: %w", val, ErrInvalidMapItem)
|
||||
}
|
||||
mKey, mVal := strings.TrimSpace(pair[0]), strings.TrimSpace(pair[1])
|
||||
|
||||
k := reflect.New(tf.Key()).Elem()
|
||||
if err := processField(mKey, k, delimiter, separator, noInit); err != nil {
|
||||
return fmt.Errorf("%s: %w", mKey, err)
|
||||
}
|
||||
|
||||
v := reflect.New(tf.Elem()).Elem()
|
||||
if err := processField(mVal, v, delimiter, separator, noInit); err != nil {
|
||||
return fmt.Errorf("%s: %w", mVal, err)
|
||||
}
|
||||
|
||||
mp.SetMapIndex(k, v)
|
||||
}
|
||||
ef.Set(mp)
|
||||
|
||||
// Slices
|
||||
case reflect.Slice:
|
||||
// Special case: []byte
|
||||
if tf.Elem().Kind() == reflect.Uint8 {
|
||||
ef.Set(reflect.ValueOf([]byte(v)))
|
||||
} else {
|
||||
vals := strings.Split(v, delimiter)
|
||||
s := reflect.MakeSlice(tf, len(vals), len(vals))
|
||||
for i, val := range vals {
|
||||
val = strings.TrimSpace(val)
|
||||
if err := processField(val, s.Index(i), delimiter, separator, noInit); err != nil {
|
||||
return fmt.Errorf("%s: %w", val, err)
|
||||
}
|
||||
}
|
||||
ef.Set(s)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateEnvName validates the given string conforms to being a valid
|
||||
// environment variable.
|
||||
//
|
||||
// Per IEEE Std 1003.1-2001 environment variables consist solely of uppercase
|
||||
// letters, digits, and _, and do not begin with a digit.
|
||||
func validateEnvName(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, r := range s {
|
||||
if (i == 0 && !isLetter(r)) || (!isLetter(r) && !isNumber(r) && r != '_') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// isLetter returns true if the given rune is a letter between a-z,A-Z. This is
|
||||
// different than unicode.IsLetter which includes all L character cases.
|
||||
func isLetter(r rune) bool {
|
||||
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
|
||||
}
|
||||
|
||||
// isNumber returns true if the given run is a number between 0-9. This is
|
||||
// different than unicode.IsNumber in that it only allows 0-9.
|
||||
func isNumber(r rune) bool {
|
||||
return r >= '0' && r <= '9'
|
||||
}
|
||||
5
vendor/modules.txt
vendored
5
vendor/modules.txt
vendored
@@ -410,6 +410,8 @@ github.com/json-iterator/go
|
||||
# github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
## explicit
|
||||
github.com/kballard/go-shellquote
|
||||
# github.com/kelseyhightower/envconfig v1.4.0
|
||||
## explicit
|
||||
# github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351
|
||||
## explicit
|
||||
github.com/kevinburke/ssh_config
|
||||
@@ -720,6 +722,9 @@ github.com/segmentio/backo-go
|
||||
# github.com/sergi/go-diff v1.1.0
|
||||
## explicit; go 1.12
|
||||
github.com/sergi/go-diff/diffmatchpatch
|
||||
# github.com/sethvargo/go-envconfig v0.8.2
|
||||
## explicit; go 1.17
|
||||
github.com/sethvargo/go-envconfig
|
||||
# github.com/sirupsen/logrus v1.8.1
|
||||
## explicit; go 1.13
|
||||
github.com/sirupsen/logrus
|
||||
|
||||
Reference in New Issue
Block a user