fix(#507): support assign --post-renderer within helmfile flags and helmdefault or release config
1. only implement post-renderer flags this patch 2. As mumoshu advise, add helmfile flags `--post-render` and add the postRenderer config in helmDefaults and release. the priority is helmfile flags > release > helmDefaults. 3. fix the test case in state_test.go and some other tests. Signed-off-by: guofutan <guofutan@tencent.com> Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
parent
1f0f7ec8d6
commit
0a953731b0
|
|
@ -60,6 +60,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||||
f.BoolVar(&applyOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
|
f.BoolVar(&applyOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
|
||||||
f.BoolVar(&applyOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
|
f.BoolVar(&applyOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
|
||||||
f.BoolVar(&applyOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`)
|
f.BoolVar(&applyOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`)
|
||||||
|
f.StringVar(&applyOptions.PostRenderer, "post-renderer", "", `pass post-renderer to "helm template" or "helm upgrade --install"`)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||||
f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
|
f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
|
||||||
f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
|
f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
|
||||||
f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`)
|
f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`)
|
||||||
|
f.StringVar(&syncOptions.PostRenderer, "post-renderer", "", `pass post-renderer to "helm template" or "helm upgrade --install"`)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ func NewTemplateCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
||||||
f.BoolVar(&templateOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`)
|
f.BoolVar(&templateOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`)
|
||||||
f.BoolVar(&templateOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
f.BoolVar(&templateOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`)
|
||||||
f.BoolVar(&templateOptions.SkipCleanup, "skip-cleanup", false, "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security")
|
f.BoolVar(&templateOptions.SkipCleanup, "skip-cleanup", false, "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security")
|
||||||
|
f.StringVar(&templateOptions.PostRenderer, "post-renderer", "", `pass post-renderer to "helm template" or "helm upgrade --install"`)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1392,6 +1392,7 @@ Do you really want to apply?
|
||||||
}
|
}
|
||||||
|
|
||||||
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
r.helm.SetPostRenderer(c.PostRenderer())
|
||||||
|
|
||||||
// We deleted releases by traversing the DAG in reverse order
|
// We deleted releases by traversing the DAG in reverse order
|
||||||
if len(releasesToBeDeleted) > 0 {
|
if len(releasesToBeDeleted) > 0 {
|
||||||
|
|
@ -1774,6 +1775,7 @@ Do you really want to sync?
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
||||||
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||||
|
r.helm.SetPostRenderer(c.PostRenderer())
|
||||||
|
|
||||||
// Traverse DAG of all the releases so that we don't suffer from false-positive missing dependencies
|
// Traverse DAG of all the releases so that we don't suffer from false-positive missing dependencies
|
||||||
st.Releases = selectedAndNeededReleases
|
st.Releases = selectedAndNeededReleases
|
||||||
|
|
|
||||||
|
|
@ -2304,6 +2304,10 @@ func (c configImpl) SkipCharts() bool {
|
||||||
return c.skipCharts
|
return c.skipCharts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c configImpl) PostRenderer() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type applyConfig struct {
|
type applyConfig struct {
|
||||||
args string
|
args string
|
||||||
values []string
|
values []string
|
||||||
|
|
@ -2334,6 +2338,7 @@ type applyConfig struct {
|
||||||
wait bool
|
wait bool
|
||||||
waitForJobs bool
|
waitForJobs bool
|
||||||
reuseValues bool
|
reuseValues bool
|
||||||
|
postRenderer string
|
||||||
|
|
||||||
// template-only options
|
// template-only options
|
||||||
includeCRDs, skipTests bool
|
includeCRDs, skipTests bool
|
||||||
|
|
@ -2473,6 +2478,10 @@ func (a applyConfig) ReuseValues() bool {
|
||||||
return a.reuseValues
|
return a.reuseValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a applyConfig) PostRenderer() string {
|
||||||
|
return a.postRenderer
|
||||||
|
}
|
||||||
|
|
||||||
type depsConfig struct {
|
type depsConfig struct {
|
||||||
skipRepos bool
|
skipRepos bool
|
||||||
includeTransitiveNeeds bool
|
includeTransitiveNeeds bool
|
||||||
|
|
@ -2555,6 +2564,11 @@ func (helm *mockHelmExec) SetHelmBinary(bin string) {
|
||||||
}
|
}
|
||||||
func (helm *mockHelmExec) SetEnableLiveOutput(enableLiveOutput bool) {
|
func (helm *mockHelmExec) SetEnableLiveOutput(enableLiveOutput bool) {
|
||||||
}
|
}
|
||||||
|
func (helm *mockHelmExec) SetPostRenderer(postRenderer string) {
|
||||||
|
}
|
||||||
|
func (helm *mockHelmExec) GetPostRenderer() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
func (helm *mockHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
func (helm *mockHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
||||||
helm.repos = append(helm.repos, mockRepo{Name: name})
|
helm.repos = append(helm.repos, mockRepo{Name: name})
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ type ReposConfigProvider interface {
|
||||||
|
|
||||||
type ApplyConfigProvider interface {
|
type ApplyConfigProvider interface {
|
||||||
Args() string
|
Args() string
|
||||||
|
PostRenderer() string
|
||||||
|
|
||||||
Values() []string
|
Values() []string
|
||||||
Set() []string
|
Set() []string
|
||||||
|
|
@ -80,6 +81,7 @@ type ApplyConfigProvider interface {
|
||||||
|
|
||||||
type SyncConfigProvider interface {
|
type SyncConfigProvider interface {
|
||||||
Args() string
|
Args() string
|
||||||
|
PostRenderer() string
|
||||||
|
|
||||||
Values() []string
|
Values() []string
|
||||||
Set() []string
|
Set() []string
|
||||||
|
|
@ -186,6 +188,7 @@ type FetchConfigProvider interface {
|
||||||
|
|
||||||
type TemplateConfigProvider interface {
|
type TemplateConfigProvider interface {
|
||||||
Args() string
|
Args() string
|
||||||
|
PostRenderer() string
|
||||||
|
|
||||||
Values() []string
|
Values() []string
|
||||||
Set() []string
|
Set() []string
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,12 @@ func (helm *noCallHelmExec) SetHelmBinary(bin string) {
|
||||||
func (helm *noCallHelmExec) SetEnableLiveOutput(enableLiveOutput bool) {
|
func (helm *noCallHelmExec) SetEnableLiveOutput(enableLiveOutput bool) {
|
||||||
helm.doPanic()
|
helm.doPanic()
|
||||||
}
|
}
|
||||||
|
func (helm *noCallHelmExec) SetPostRenderer(postRenderer string) {
|
||||||
|
}
|
||||||
|
func (helm *noCallHelmExec) GetPostRenderer() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (helm *noCallHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
func (helm *noCallHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
||||||
helm.doPanic()
|
helm.doPanic()
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -112,13 +112,6 @@ func GetArgs(args string, state *state.HelmState) []string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(state.HelmDefaults.PostRenderer) > 0 {
|
|
||||||
argArr = append(argArr, fmt.Sprintf("--post-renderer=\"%s\"", state.HelmDefaults.PostRenderer))
|
|
||||||
}
|
|
||||||
for _, arg := range state.HelmDefaults.PostRendererArgs {
|
|
||||||
argArr = append(argArr, fmt.Sprintf("--post-renderer-args=\"%s\"", arg))
|
|
||||||
}
|
|
||||||
|
|
||||||
state.HelmDefaults.Args = argArr
|
state.HelmDefaults.Args = argArr
|
||||||
|
|
||||||
return state.HelmDefaults.Args
|
return state.HelmDefaults.Args
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,6 @@ func TestGetArgs(t *testing.T) {
|
||||||
defaultArgs: []string{"--recreate-pods", "--force"},
|
defaultArgs: []string{"--recreate-pods", "--force"},
|
||||||
expected: "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false,app3.bootstrap=true --tiller-namespace ns --recreate-pods --force",
|
expected: "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false,app3.bootstrap=true --tiller-namespace ns --recreate-pods --force",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
args: "--post-renderer=aaa --post-renderer-args=bbb",
|
|
||||||
expected: "--post-renderer=aaa --post-renderer-args=bbb",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: "--post-renderer aaa --post-renderer-args bbb",
|
|
||||||
expected: "--post-renderer aaa --post-renderer-args bbb",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
Helmdefaults := state.HelmSpec{KubeContext: "test", TillerNamespace: "test-namespace", Args: test.defaultArgs}
|
Helmdefaults := state.HelmSpec{KubeContext: "test", TillerNamespace: "test-namespace", Args: test.defaultArgs}
|
||||||
|
|
@ -57,38 +49,6 @@ func TestGetArgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetArgs_PostRenderer(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
postRenderer string
|
|
||||||
PostRendererArgs []string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
postRenderer: "sed",
|
|
||||||
PostRendererArgs: []string{"-i", "s/aaa/bb/g"},
|
|
||||||
expected: "--post-renderer=\"sed\" --post-renderer-args=\"-i\" --post-renderer-args=\"s/aaa/bb/g\"",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
postRenderer: "sed",
|
|
||||||
PostRendererArgs: []string{"-i", "s/aa a/b b/g"},
|
|
||||||
expected: "--post-renderer=\"sed\" --post-renderer-args=\"-i\" --post-renderer-args=\"s/aa a/b b/g\"",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
Helmdefaults := state.HelmSpec{KubeContext: "test", TillerNamespace: "test-namespace", PostRenderer: test.postRenderer, PostRendererArgs: test.PostRendererArgs}
|
|
||||||
testState := &state.HelmState{
|
|
||||||
ReleaseSetSpec: state.ReleaseSetSpec{
|
|
||||||
HelmDefaults: Helmdefaults,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
receivedArgs := GetArgs("", testState)
|
|
||||||
|
|
||||||
require.Equalf(t, test.expected, strings.Join(receivedArgs, " "), "expected args %s, received args %s", test.expected, strings.Join(receivedArgs, " "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestIsNewFlag tests the isNewFlag function
|
// TestIsNewFlag tests the isNewFlag function
|
||||||
func TestIsNewFlag(t *testing.T) {
|
func TestIsNewFlag(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ type ApplyOptions struct {
|
||||||
WaitForJobs bool
|
WaitForJobs bool
|
||||||
// ReuseValues is true if the helm command should reuse the values
|
// ReuseValues is true if the helm command should reuse the values
|
||||||
ReuseValues bool
|
ReuseValues bool
|
||||||
|
// Propagate '--postRenderer' to helmv3 template and helm install
|
||||||
|
PostRenderer string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApply creates a new Apply
|
// NewApply creates a new Apply
|
||||||
|
|
@ -193,3 +195,8 @@ func (a *ApplyImpl) WaitForJobs() bool {
|
||||||
func (a *ApplyImpl) ReuseValues() bool {
|
func (a *ApplyImpl) ReuseValues() bool {
|
||||||
return a.ApplyOptions.ReuseValues
|
return a.ApplyOptions.ReuseValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostRenderer returns the PostRenderer.
|
||||||
|
func (a *ApplyImpl) PostRenderer() string {
|
||||||
|
return a.ApplyOptions.PostRenderer
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ type SyncOptions struct {
|
||||||
WaitForJobs bool
|
WaitForJobs bool
|
||||||
// ReuseValues is true if the helm command should reuse the values
|
// ReuseValues is true if the helm command should reuse the values
|
||||||
ReuseValues bool
|
ReuseValues bool
|
||||||
|
// Propagate '--postRenderer' to helmv3 template and helm install
|
||||||
|
PostRenderer string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSyncOptions creates a new Apply
|
// NewSyncOptions creates a new Apply
|
||||||
|
|
@ -110,3 +112,8 @@ func (t *SyncImpl) WaitForJobs() bool {
|
||||||
func (t *SyncImpl) ReuseValues() bool {
|
func (t *SyncImpl) ReuseValues() bool {
|
||||||
return t.SyncOptions.ReuseValues
|
return t.SyncOptions.ReuseValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostRenderer returns the PostRenderer.
|
||||||
|
func (t *SyncImpl) PostRenderer() string {
|
||||||
|
return t.SyncOptions.PostRenderer
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ type TemplateOptions struct {
|
||||||
SkipDeps bool
|
SkipDeps bool
|
||||||
// SkipCleanup is the skip cleanup flag
|
// SkipCleanup is the skip cleanup flag
|
||||||
SkipCleanup bool
|
SkipCleanup bool
|
||||||
|
// Propagate '--postRenderer' to helmv3 template and helm install
|
||||||
|
PostRenderer string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTemplateOptions creates a new Apply
|
// NewTemplateOptions creates a new Apply
|
||||||
|
|
@ -123,3 +125,8 @@ func (t *TemplateImpl) Validate() bool {
|
||||||
func (t *TemplateImpl) Values() []string {
|
func (t *TemplateImpl) Values() []string {
|
||||||
return t.TemplateOptions.Values
|
return t.TemplateOptions.Values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostRenderer returns the PostRenderer.
|
||||||
|
func (t *TemplateImpl) PostRenderer() string {
|
||||||
|
return t.TemplateOptions.PostRenderer
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,11 @@ func (helm *Helm) SetHelmBinary(bin string) {
|
||||||
}
|
}
|
||||||
func (helm *Helm) SetEnableLiveOutput(enableLiveOutput bool) {
|
func (helm *Helm) SetEnableLiveOutput(enableLiveOutput bool) {
|
||||||
}
|
}
|
||||||
|
func (helm *Helm) SetPostRenderer(postRenderer string) {
|
||||||
|
}
|
||||||
|
func (helm *Helm) GetPostRenderer() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
func (helm *Helm) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
func (helm *Helm) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
||||||
helm.Repo = []string{name, repository, cafile, certfile, keyfile, username, password, managed, passCredentials, skipTLSVerify}
|
helm.Repo = []string{name, repository, cafile, certfile, keyfile, username, password, managed, passCredentials, skipTLSVerify}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -200,7 +205,10 @@ func (helm *Helm) ChartExport(chart string, path string, flags ...string) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (helm *Helm) IsHelm3() bool {
|
func (helm *Helm) IsHelm3() bool {
|
||||||
return helm.Helm3
|
if helm.Version == nil {
|
||||||
|
return helm.Helm3
|
||||||
|
}
|
||||||
|
return helm.Version.Major() == 3
|
||||||
}
|
}
|
||||||
|
|
||||||
func (helm *Helm) GetVersion() helmexec.Version {
|
func (helm *Helm) GetVersion() helmexec.Version {
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ type execer struct {
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
kubeContext string
|
kubeContext string
|
||||||
extra []string
|
extra []string
|
||||||
postRenderers []string
|
postRenderer string
|
||||||
decryptedSecretMutex sync.Mutex
|
decryptedSecretMutex sync.Mutex
|
||||||
decryptedSecrets map[string]*decryptedSecret
|
decryptedSecrets map[string]*decryptedSecret
|
||||||
writeTempFile func([]byte) (string, error)
|
writeTempFile func([]byte) (string, error)
|
||||||
|
|
@ -132,23 +132,7 @@ func New(helmBinary string, enableLiveOutput bool, logger *zap.SugaredLogger, ku
|
||||||
}
|
}
|
||||||
|
|
||||||
func (helm *execer) SetExtraArgs(args ...string) {
|
func (helm *execer) SetExtraArgs(args ...string) {
|
||||||
var extraArgs []string
|
helm.extra = args
|
||||||
var renderArgs []string
|
|
||||||
// reset the postRenderers and filter --post-renderer=xx or --post-renderer xxx from args and put into helm.postRenderers
|
|
||||||
for i := 0; i < len(args); i++ {
|
|
||||||
if strings.HasPrefix(args[i], "--post-renderer=") || strings.HasPrefix(args[i], "--post-renderer-args=") {
|
|
||||||
renderArgs = append(renderArgs, args[i])
|
|
||||||
} else if (args[i] == "--post-renderer" || args[i] == "--post-renderer-args") && i < len(args)-1 {
|
|
||||||
renderArgs = append(renderArgs, args[i])
|
|
||||||
renderArgs = append(renderArgs, args[i+1])
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
extraArgs = append(extraArgs, args[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
helm.extra = extraArgs
|
|
||||||
helm.postRenderers = renderArgs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (helm *execer) SetHelmBinary(bin string) {
|
func (helm *execer) SetHelmBinary(bin string) {
|
||||||
|
|
@ -159,6 +143,14 @@ func (helm *execer) SetEnableLiveOutput(enableLiveOutput bool) {
|
||||||
helm.enableLiveOutput = enableLiveOutput
|
helm.enableLiveOutput = enableLiveOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (helm *execer) SetPostRenderer(postRenderer string) {
|
||||||
|
helm.postRenderer = postRenderer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (helm *execer) GetPostRenderer() string {
|
||||||
|
return helm.postRenderer
|
||||||
|
}
|
||||||
|
|
||||||
func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error {
|
||||||
var args []string
|
var args []string
|
||||||
var out []byte
|
var out []byte
|
||||||
|
|
@ -268,8 +260,8 @@ func (helm *execer) SyncRelease(context HelmContext, name, chart string, flags .
|
||||||
env["HELM_TILLER_HISTORY_MAX"] = strconv.Itoa(context.HistoryMax)
|
env["HELM_TILLER_HISTORY_MAX"] = strconv.Itoa(context.HistoryMax)
|
||||||
}
|
}
|
||||||
|
|
||||||
if helm.IsHelm3() {
|
if helm.IsHelm3() && helm.postRenderer != "" {
|
||||||
flags = append(flags, helm.postRenderers...)
|
flags = append(flags, "--post-renderer", helm.postRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := helm.exec(append(append(preArgs, "upgrade", "--install", name, chart), flags...), env, nil)
|
out, err := helm.exec(append(append(preArgs, "upgrade", "--install", name, chart), flags...), env, nil)
|
||||||
|
|
@ -411,10 +403,9 @@ func (helm *execer) TemplateRelease(name string, chart string, flags ...string)
|
||||||
args = []string{"template", chart, "--name", name}
|
args = []string{"template", chart, "--name", name}
|
||||||
}
|
}
|
||||||
|
|
||||||
if helm.IsHelm3() {
|
if helm.IsHelm3() && helm.postRenderer != "" {
|
||||||
flags = append(flags, helm.postRenderers...)
|
flags = append(flags, "--post-renderer", helm.postRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := helm.exec(append(args, flags...), map[string]string{}, nil)
|
out, err := helm.exec(append(args, flags...), map[string]string{}, nil)
|
||||||
|
|
||||||
var outputToFile bool
|
var outputToFile bool
|
||||||
|
|
|
||||||
|
|
@ -69,29 +69,6 @@ func Test_SetExtraArgs(t *testing.T) {
|
||||||
if !reflect.DeepEqual(helm.extra, []string{"alpha", "beta"}) {
|
if !reflect.DeepEqual(helm.extra, []string{"alpha", "beta"}) {
|
||||||
t.Error("helmexec.SetExtraArgs() - two extra arguments missing (overwriting the previous value)")
|
t.Error("helmexec.SetExtraArgs() - two extra arguments missing (overwriting the previous value)")
|
||||||
}
|
}
|
||||||
|
|
||||||
helm.SetExtraArgs("--post-renderer=aaa")
|
|
||||||
fmt.Println(helm.postRenderers)
|
|
||||||
if !reflect.DeepEqual(helm.postRenderers, []string{"--post-renderer=aaa"}) {
|
|
||||||
t.Error("helmexec.SetExtraArgs() - post-renderer assign arguments missing ")
|
|
||||||
}
|
|
||||||
|
|
||||||
helm.SetExtraArgs("--post-renderer", "aaa")
|
|
||||||
fmt.Println(helm.postRenderers)
|
|
||||||
if !reflect.DeepEqual(helm.postRenderers, []string{"--post-renderer", "aaa"}) {
|
|
||||||
t.Error("helmexec.SetExtraArgs() - post-renderer blank arguments missing ")
|
|
||||||
}
|
|
||||||
|
|
||||||
helm.SetExtraArgs("--post-renderer-args=bbb")
|
|
||||||
fmt.Println(helm.postRenderers)
|
|
||||||
if !reflect.DeepEqual(helm.postRenderers, []string{"--post-renderer-args=bbb"}) {
|
|
||||||
t.Error("helmexec.SetExtraArgs() - post-renderer-args assign arguments missing")
|
|
||||||
}
|
|
||||||
|
|
||||||
helm.SetExtraArgs("--post-renderer", "aaa", "--post-renderer-args=bbb")
|
|
||||||
if !reflect.DeepEqual(helm.postRenderers, []string{"--post-renderer", "aaa", "--post-renderer-args=bbb"}) {
|
|
||||||
t.Error("helmexec.SetExtraArgs() - post-renderer arguments not be set correct")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_SetHelmBinary(t *testing.T) {
|
func Test_SetHelmBinary(t *testing.T) {
|
||||||
|
|
@ -116,6 +93,30 @@ func Test_SetEnableLiveOutput(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_SetPostRenderer(t *testing.T) {
|
||||||
|
helm := MockExecer(NewLogger(os.Stdout, "info"), "dev")
|
||||||
|
if helm.enableLiveOutput {
|
||||||
|
t.Error("helmexec.enableLiveOutput should not be enabled by default")
|
||||||
|
}
|
||||||
|
postRendererFoo := "/bin/rewrite-repo.sh"
|
||||||
|
helm.SetPostRenderer(postRendererFoo)
|
||||||
|
if helm.postRenderer != postRendererFoo {
|
||||||
|
t.Errorf("helmexec.SetPostRenderer() - actual = %s expect = %s", helm.postRenderer, postRendererFoo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_GetPostRenderer(t *testing.T) {
|
||||||
|
helm := MockExecer(NewLogger(os.Stdout, "info"), "dev")
|
||||||
|
if helm.enableLiveOutput {
|
||||||
|
t.Error("helmexec.enableLiveOutput should not be enabled by default")
|
||||||
|
}
|
||||||
|
postRendererFoo := "/bin/rewrite-repo.sh"
|
||||||
|
helm.SetPostRenderer(postRendererFoo)
|
||||||
|
if helm.GetPostRenderer() != postRendererFoo {
|
||||||
|
t.Errorf("helmexec.GetPostRenderer() - actual = %s expect = %s", helm.GetPostRenderer(), postRendererFoo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_AddRepo_Helm_3_3_2(t *testing.T) {
|
func Test_AddRepo_Helm_3_3_2(t *testing.T) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
logger := NewLogger(&buffer, "debug")
|
logger := NewLogger(&buffer, "debug")
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ type Interface interface {
|
||||||
SetExtraArgs(args ...string)
|
SetExtraArgs(args ...string)
|
||||||
SetHelmBinary(bin string)
|
SetHelmBinary(bin string)
|
||||||
SetEnableLiveOutput(enableLiveOutput bool)
|
SetEnableLiveOutput(enableLiveOutput bool)
|
||||||
|
SetPostRenderer(postRenderer string)
|
||||||
|
GetPostRenderer() string
|
||||||
|
|
||||||
AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error
|
AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string, passCredentials string, skipTLSVerify string) error
|
||||||
UpdateRepo() error
|
UpdateRepo() error
|
||||||
|
|
|
||||||
|
|
@ -121,13 +121,11 @@ type SubhelmfileEnvironmentSpec struct {
|
||||||
|
|
||||||
// HelmSpec to defines helmDefault values
|
// HelmSpec to defines helmDefault values
|
||||||
type HelmSpec struct {
|
type HelmSpec struct {
|
||||||
KubeContext string `yaml:"kubeContext,omitempty"`
|
KubeContext string `yaml:"kubeContext,omitempty"`
|
||||||
TillerNamespace string `yaml:"tillerNamespace,omitempty"`
|
TillerNamespace string `yaml:"tillerNamespace,omitempty"`
|
||||||
Tillerless bool `yaml:"tillerless"`
|
Tillerless bool `yaml:"tillerless"`
|
||||||
Args []string `yaml:"args,omitempty"`
|
Args []string `yaml:"args,omitempty"`
|
||||||
PostRenderer string `yaml:"postRenderer,omitempty"`
|
Verify bool `yaml:"verify"`
|
||||||
PostRendererArgs []string `yaml:"postRendererArgs,omitempty"`
|
|
||||||
Verify bool `yaml:"verify"`
|
|
||||||
// Devel, when set to true, use development versions, too. Equivalent to version '>0.0.0-0'
|
// Devel, when set to true, use development versions, too. Equivalent to version '>0.0.0-0'
|
||||||
Devel bool `yaml:"devel"`
|
Devel bool `yaml:"devel"`
|
||||||
// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
|
// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
|
||||||
|
|
@ -154,6 +152,8 @@ type HelmSpec struct {
|
||||||
SkipDeps bool `yaml:"skipDeps"`
|
SkipDeps bool `yaml:"skipDeps"`
|
||||||
// on helm upgrade/diff, reuse values currently set in the release and merge them with the ones defined within helmfile
|
// on helm upgrade/diff, reuse values currently set in the release and merge them with the ones defined within helmfile
|
||||||
ReuseValues bool `yaml:"reuseValues"`
|
ReuseValues bool `yaml:"reuseValues"`
|
||||||
|
// Propagate '--postRenderer' to helmv3 template and helm install
|
||||||
|
PostRenderer *string `yaml:"postRenderer,omitempty"`
|
||||||
|
|
||||||
TLS bool `yaml:"tls"`
|
TLS bool `yaml:"tls"`
|
||||||
TLSCACert string `yaml:"tlsCACert,omitempty"`
|
TLSCACert string `yaml:"tlsCACert,omitempty"`
|
||||||
|
|
@ -317,6 +317,9 @@ type ReleaseSpec struct {
|
||||||
// This is relevant only when your release uses a local chart or a directory containing K8s manifests or a Kustomization
|
// This is relevant only when your release uses a local chart or a directory containing K8s manifests or a Kustomization
|
||||||
// as a Helm chart.
|
// as a Helm chart.
|
||||||
SkipDeps *bool `yaml:"skipDeps,omitempty"`
|
SkipDeps *bool `yaml:"skipDeps,omitempty"`
|
||||||
|
|
||||||
|
// Propagate '--postRenderer' to helmv3 template and helm install
|
||||||
|
PostRenderer *string `yaml:"postRenderer,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChartPathOrName returns ChartPath if it is non-empty, and returns Chart otherwise.
|
// ChartPathOrName returns ChartPath if it is non-empty, and returns Chart otherwise.
|
||||||
|
|
@ -2515,6 +2518,14 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if helm.IsHelm3() && helm.GetPostRenderer() == "" {
|
||||||
|
if release.PostRenderer != nil && *release.PostRenderer != "" {
|
||||||
|
flags = append(flags, "--post-renderer", *release.PostRenderer)
|
||||||
|
} else if st.HelmDefaults.PostRenderer != nil && *st.HelmDefaults.PostRenderer != "" {
|
||||||
|
flags = append(flags, "--post-renderer", *st.HelmDefaults.PostRenderer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
common, clean, err := st.namespaceAndValuesFlags(helm, release, workerIndex)
|
common, clean, err := st.namespaceAndValuesFlags(helm, release, workerIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, clean, err
|
return nil, clean, err
|
||||||
|
|
@ -2542,6 +2553,14 @@ func (st *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseS
|
||||||
|
|
||||||
flags = st.appendApiVersionsFlags(flags, release)
|
flags = st.appendApiVersionsFlags(flags, release)
|
||||||
|
|
||||||
|
if helm.IsHelm3() && helm.GetPostRenderer() == "" {
|
||||||
|
if release.PostRenderer != nil && *release.PostRenderer != "" {
|
||||||
|
flags = append(flags, "--post-renderer", *release.PostRenderer)
|
||||||
|
} else if st.HelmDefaults.PostRenderer != nil && *st.HelmDefaults.PostRenderer != "" {
|
||||||
|
flags = append(flags, "--post-renderer", *st.HelmDefaults.PostRenderer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
common, files, err := st.namespaceAndValuesFlags(helm, release, workerIndex)
|
common, files, err := st.namespaceAndValuesFlags(helm, release, workerIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, files, err
|
return nil, files, err
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,8 @@ func boolValue(v bool) *bool {
|
||||||
func TestHelmState_flagsForUpgrade(t *testing.T) {
|
func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||||
enable := true
|
enable := true
|
||||||
disable := false
|
disable := false
|
||||||
|
postRendererDefault := "foo-default.sh"
|
||||||
|
postRendererRelease := "foo-release.sh"
|
||||||
some := func(v int) *int {
|
some := func(v int) *int {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
@ -693,6 +694,73 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantErr: "releases[].createNamespace requires Helm 3.2.0 or greater",
|
wantErr: "releases[].createNamespace requires Helm 3.2.0 or greater",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "post-renderer-flags-use-helmdefault",
|
||||||
|
defaults: HelmSpec{
|
||||||
|
Verify: false,
|
||||||
|
CreateNamespace: &enable,
|
||||||
|
PostRenderer: &postRendererDefault,
|
||||||
|
},
|
||||||
|
version: semver.MustParse("3.10.0"),
|
||||||
|
release: &ReleaseSpec{
|
||||||
|
Chart: "test/chart",
|
||||||
|
Version: "0.1",
|
||||||
|
Verify: &disable,
|
||||||
|
Name: "test-charts",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
CreateNamespace: &disable,
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"--version", "0.1",
|
||||||
|
"--post-renderer", postRendererDefault,
|
||||||
|
"--namespace", "test-namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post-renderer-flags-use-release",
|
||||||
|
defaults: HelmSpec{
|
||||||
|
Verify: false,
|
||||||
|
CreateNamespace: &enable,
|
||||||
|
},
|
||||||
|
version: semver.MustParse("3.10.0"),
|
||||||
|
release: &ReleaseSpec{
|
||||||
|
Chart: "test/chart",
|
||||||
|
Version: "0.1",
|
||||||
|
Verify: &disable,
|
||||||
|
Name: "test-charts",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
CreateNamespace: &disable,
|
||||||
|
PostRenderer: &postRendererRelease,
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"--version", "0.1",
|
||||||
|
"--post-renderer", postRendererRelease,
|
||||||
|
"--namespace", "test-namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post-renderer-flags-use-release-prior-helmdefault",
|
||||||
|
defaults: HelmSpec{
|
||||||
|
Verify: false,
|
||||||
|
CreateNamespace: &enable,
|
||||||
|
PostRenderer: &postRendererDefault,
|
||||||
|
},
|
||||||
|
version: semver.MustParse("3.10.0"),
|
||||||
|
release: &ReleaseSpec{
|
||||||
|
Chart: "test/chart",
|
||||||
|
Version: "0.1",
|
||||||
|
Verify: &disable,
|
||||||
|
Name: "test-charts",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
CreateNamespace: &disable,
|
||||||
|
PostRenderer: &postRendererRelease,
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"--version", "0.1",
|
||||||
|
"--post-renderer", postRendererRelease,
|
||||||
|
"--namespace", "test-namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for i := range tests {
|
for i := range tests {
|
||||||
tt := tests[i]
|
tt := tests[i]
|
||||||
|
|
@ -724,6 +792,118 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHelmState_flagsForTemplate(t *testing.T) {
|
||||||
|
enable := true
|
||||||
|
disable := false
|
||||||
|
postRendererDefault := "foo-default.sh"
|
||||||
|
postRendererRelease := "foo-release.sh"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
version *semver.Version
|
||||||
|
defaults HelmSpec
|
||||||
|
release *ReleaseSpec
|
||||||
|
want []string
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "post-renderer-flags-use-helmdefault",
|
||||||
|
defaults: HelmSpec{
|
||||||
|
Verify: false,
|
||||||
|
CreateNamespace: &enable,
|
||||||
|
PostRenderer: &postRendererDefault,
|
||||||
|
},
|
||||||
|
version: semver.MustParse("3.10.0"),
|
||||||
|
release: &ReleaseSpec{
|
||||||
|
Chart: "test/chart",
|
||||||
|
Version: "0.1",
|
||||||
|
Verify: &disable,
|
||||||
|
Name: "test-charts",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
CreateNamespace: &disable,
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"--version", "0.1",
|
||||||
|
"--post-renderer", postRendererDefault,
|
||||||
|
"--namespace", "test-namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post-renderer-flags-use-release",
|
||||||
|
defaults: HelmSpec{
|
||||||
|
Verify: false,
|
||||||
|
CreateNamespace: &enable,
|
||||||
|
},
|
||||||
|
version: semver.MustParse("3.10.0"),
|
||||||
|
release: &ReleaseSpec{
|
||||||
|
Chart: "test/chart",
|
||||||
|
Version: "0.1",
|
||||||
|
Verify: &disable,
|
||||||
|
Name: "test-charts",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
CreateNamespace: &disable,
|
||||||
|
PostRenderer: &postRendererRelease,
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"--version", "0.1",
|
||||||
|
"--post-renderer", postRendererRelease,
|
||||||
|
"--namespace", "test-namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post-renderer-flags-use-release-prior-helmdefault",
|
||||||
|
defaults: HelmSpec{
|
||||||
|
Verify: false,
|
||||||
|
CreateNamespace: &enable,
|
||||||
|
PostRenderer: &postRendererDefault,
|
||||||
|
},
|
||||||
|
version: semver.MustParse("3.10.0"),
|
||||||
|
release: &ReleaseSpec{
|
||||||
|
Chart: "test/chart",
|
||||||
|
Version: "0.1",
|
||||||
|
Verify: &disable,
|
||||||
|
Name: "test-charts",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
CreateNamespace: &disable,
|
||||||
|
PostRenderer: &postRendererRelease,
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"--version", "0.1",
|
||||||
|
"--post-renderer", postRendererRelease,
|
||||||
|
"--namespace", "test-namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i := range tests {
|
||||||
|
tt := tests[i]
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
state := &HelmState{
|
||||||
|
basePath: "./",
|
||||||
|
ReleaseSetSpec: ReleaseSetSpec{
|
||||||
|
DeprecatedContext: "default",
|
||||||
|
Releases: []ReleaseSpec{*tt.release},
|
||||||
|
HelmDefaults: tt.defaults,
|
||||||
|
},
|
||||||
|
valsRuntime: valsRuntime,
|
||||||
|
}
|
||||||
|
helm := &exectest.Helm{
|
||||||
|
Version: tt.version,
|
||||||
|
}
|
||||||
|
|
||||||
|
args, _, err := state.flagsForTemplate(helm, tt.release, 0)
|
||||||
|
if err != nil && tt.wantErr == "" {
|
||||||
|
t.Errorf("unexpected error flagsForUpgrade: %v", err)
|
||||||
|
}
|
||||||
|
if tt.wantErr != "" && (err == nil || err.Error() != tt.wantErr) {
|
||||||
|
t.Errorf("expected error '%v'; got '%v'", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(args, tt.want) {
|
||||||
|
t.Errorf("flagsForUpgrade returned = %v, want %v", args, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_isLocalChart(t *testing.T) {
|
func Test_isLocalChart(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
chart string
|
chart string
|
||||||
|
|
@ -1054,6 +1234,7 @@ func TestHelmState_SyncRepos(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHelmState_SyncReleases(t *testing.T) {
|
func TestHelmState_SyncReleases(t *testing.T) {
|
||||||
|
postRenderer := "foo.sh"
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
releases []ReleaseSpec
|
releases []ReleaseSpec
|
||||||
|
|
@ -1150,6 +1331,20 @@ func TestHelmState_SyncReleases(t *testing.T) {
|
||||||
helm: &exectest.Helm{},
|
helm: &exectest.Helm{},
|
||||||
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "foo.bar[0]={A,B}", "--reset-values"}}},
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "foo.bar[0]={A,B}", "--reset-values"}}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "post renderer",
|
||||||
|
releases: []ReleaseSpec{
|
||||||
|
{
|
||||||
|
Name: "releaseName",
|
||||||
|
Chart: "foo",
|
||||||
|
PostRenderer: &postRenderer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
helm: &exectest.Helm{
|
||||||
|
Helm3: true,
|
||||||
|
},
|
||||||
|
wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--post-renderer", postRenderer, "--reset-values"}}},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for i := range tests {
|
for i := range tests {
|
||||||
tt := tests[i]
|
tt := tests[i]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue