Repository: iter8-tools/iter8
Pull request #1229 (2022-04-25 14:53:36)

Fixing Kubernetes experimentation bugs
1. Creating a secret also requires waiting for permissions. 
2. Also increasing the timeouts for readiness [due to these failed tests](https://github.com/iter8-tools/iter8/runs/6158808866?check_suite_focus=true).
3. Improving variable/flag naming along with documentation for remote Iter8 charts URL.

Signed-off-by: Srinivasan Parthasarathy <spartha@us.ibm.com>


Patch:
diff --git a/.github/workflows/assets.yaml b/.github/workflows/assets.yaml
index 8baa0136..62b81e77 100644
--- a/.github/workflows/assets.yaml
+++ b/.github/workflows/assets.yaml
@@ -117,7 +117,7 @@ jobs:
         --set payloadURL="https://httpbin.org/stream/1"
     - name: try other iter8 k commands
       run: |
-        iter8 k assert -c completed -c nofailure --timeout 60s
+        iter8 k assert -c completed -c nofailure --timeout 300s
         iter8 k report
         iter8 k log
         iter8 k delete
@@ -147,7 +147,7 @@ jobs:
         --set protoURL="https://raw.githubusercontent.com/grpc/grpc-go/master/examples/helloworld/helloworld/helloworld.proto"
     - name: try other iter8 k commands
       run: |
-        iter8 k assert -c completed -c nofailure --timeout 60s
+        iter8 k assert -c completed -c nofailure --timeout 300s
         iter8 k report
         iter8 k log
         iter8 k delete        
\ No newline at end of file
diff --git a/action/hub.go b/action/hub.go
index 301b613a..b062de7a 100644
--- a/action/hub.go
+++ b/action/hub.go
@@ -13,13 +13,15 @@ const defaultIter8Repo = "github.com/iter8-tools/iter8.git"
 
 // HubOpts are the options used for downloading Iter8 experiment charts
 type HubOpts struct {
-	// Folder is the full path to the Iter8 experiment charts folder
-	Folder string
+	// RemoteFolderURL is the URL of the remote Iter8 experiment charts folder
+	// Remote URLs can be any go-getter URLs like GitHub or GitLab URLs
+	// https://github.com/hashicorp/go-getter
+	RemoteFolderURL string
 	// ChartsDir is the full path to the `charts` dir
 	ChartsDir string
 }
 
-func DefaultFolder() string {
+func DefaultRemoteFolderURL() string {
 	// parse version
 	v := strings.Split(base.Version, "-")
 	ref := v[0]
@@ -30,15 +32,15 @@ func DefaultFolder() string {
 func NewHubOpts() *HubOpts {
 
 	return &HubOpts{
-		Folder:    DefaultFolder(),
-		ChartsDir: chartsFolderName,
+		RemoteFolderURL: DefaultRemoteFolderURL(),
+		ChartsDir:       chartsFolderName,
 	}
 }
 
 // LocalRun downloads an experiment chart to DestDir
 func (hub *HubOpts) LocalRun() error {
-	log.Logger.Infof("downloading %v into %v", hub.Folder, hub.ChartsDir)
-	if err := getter.Get(hub.ChartsDir, hub.Folder); err != nil {
+	log.Logger.Infof("downloading %v into %v", hub.RemoteFolderURL, hub.ChartsDir)
+	if err := getter.Get(hub.ChartsDir, hub.RemoteFolderURL); err != nil {
 		e := errors.New("unable to download charts")
 		log.Logger.WithStackTrace(err.Error()).Error(e)
 		return e
diff --git a/action/launch.go b/action/launch.go
index cf606385..d74e1dcc 100644
--- a/action/launch.go
+++ b/action/launch.go
@@ -12,8 +12,10 @@ import (
 type LaunchOpts struct {
 	// DryRun enables simulating a launch
 	DryRun bool
-	// Folder is the full path to the Iter8 experiment charts folder
-	Folder string
+	// RemoteFolderURL is the URL of the remote Iter8 experiment charts folder
+	// Remote URLs can be any go-getter URLs like GitHub or GitLab URLs
+	// https://github.com/hashicorp/go-getter
+	RemoteFolderURL string
 	// ChartsParentDir is the directory where `charts` is to be downloaded or is located
 	ChartsParentDir string
 	// NoDownload disables charts download.
@@ -33,7 +35,7 @@ type LaunchOpts struct {
 func NewLaunchOpts(kd *driver.KubeDriver) *LaunchOpts {
 	return &LaunchOpts{
 		DryRun:          false,
-		Folder:          DefaultFolder(),
+		RemoteFolderURL: DefaultRemoteFolderURL(),
 		ChartsParentDir: ".",
 		NoDownload:      false,
 		ChartName:       "",
@@ -49,8 +51,8 @@ func (lOpts *LaunchOpts) LocalRun() error {
 	if !lOpts.NoDownload {
 		// download chart from Iter8 hub
 		hOpts := &HubOpts{
-			Folder:    lOpts.Folder,
-			ChartsDir: path.Join(lOpts.ChartsParentDir, chartsFolderName),
+			RemoteFolderURL: lOpts.RemoteFolderURL,
+			ChartsDir:       path.Join(lOpts.ChartsParentDir, chartsFolderName),
 		}
 		if err := hOpts.LocalRun(); err != nil {
 			return err
@@ -97,8 +99,8 @@ func (lOpts *LaunchOpts) KubeRun() error {
 	if !lOpts.NoDownload {
 		// download chart from Iter8 hub
 		hOpts := &HubOpts{
-			Folder:    lOpts.Folder,
-			ChartsDir: path.Join(lOpts.ChartsParentDir, chartsFolderName),
+			RemoteFolderURL: lOpts.RemoteFolderURL,
+			ChartsDir:       path.Join(lOpts.ChartsParentDir, chartsFolderName),
 		}
 		if err := hOpts.LocalRun(); err != nil {
 			return err
diff --git a/cmd/hub.go b/cmd/hub.go
index 541ae8ed..4a52e95b 100644
--- a/cmd/hub.go
+++ b/cmd/hub.go
@@ -28,13 +28,13 @@ func newHubCmd() *cobra.Command {
 			return actor.LocalRun()
 		},
 	}
-	addFolderFlag(cmd, &actor.Folder)
+	addRemoteFolderURLFlag(cmd, &actor.RemoteFolderURL)
 	return cmd
 }
 
-// addFolderFlag
-func addFolderFlag(cmd *cobra.Command, folderPtr *string) {
-	cmd.Flags().StringVar(folderPtr, "folder", ia.DefaultFolder(), "URL of the folder containing Iter8 experiment charts. Accepts any URL supported https://github.com/hashicorp/go-getter")
+// add the remoteFolderURL flag to the command
+func addRemoteFolderURLFlag(cmd *cobra.Command, remoteFolderURLPtr *string) {
+	cmd.Flags().StringVar(remoteFolderURLPtr, "remoteFolderURL", ia.DefaultRemoteFolderURL(), "URL of the remote folder containing Iter8 experiment charts. Accepts any URL supported by https://github.com/hashicorp/go-getter")
 }
 
 // initialize with the hub command
diff --git a/cmd/klaunch.go b/cmd/klaunch.go
index 098d932d..79bbad80 100644
--- a/cmd/klaunch.go
+++ b/cmd/klaunch.go
@@ -23,9 +23,9 @@ Use the dry option to simulate a Kubernetes experiment. This creates the manifes
 
 
 Launching an experiment requires the Iter8 experiment charts folder. You can use various launch flags to control:
-	1. Whether Iter8 should download the experiment charts folder a remote source (example, a Git folder), or reuse local charts.
+	1. Whether Iter8 should download the experiment charts folder from a remote URL (example, a GitHub URL), or reuse local charts.
 	2. The parent directory of the charts folder.
-	3. The remote source (example, a Git folder) from which charts are downloaded.
+	3. The remote URL (example, a GitHub URL) from which charts are downloaded.
 `
 
 // newKLaunchCmd creates the Kubernetes launch command
@@ -48,7 +48,7 @@ func newKLaunchCmd(kd *driver.KubeDriver, out io.Writer) *cobra.Command {
 
 	// flags shared with launch
 	addChartsParentDirFlag(cmd, &actor.ChartsParentDir)
-	addFolderFlag(cmd, &actor.Folder)
+	addRemoteFolderURLFlag(cmd, &actor.RemoteFolderURL)
 	addChartNameFlag(cmd, &actor.ChartName)
 	addValueFlags(cmd.Flags(), &actor.Options)
 	addNoDownloadFlag(cmd, &actor.NoDownload)
diff --git a/cmd/launch.go b/cmd/launch.go
index 8a0f14d8..40c9cfb5 100644
--- a/cmd/launch.go
+++ b/cmd/launch.go
@@ -19,9 +19,9 @@ Use the dry option to simulate an experiment. This creates experiment.yaml and *
 	  --dry
 
 Launching an experiment requires the Iter8 experiment charts folder. You can use various launch flags to control:
-	1. Whether Iter8 should download the experiment charts folder a remote source (example, a Git folder), or reuse local charts.
-	2. The parent directory of the charts folder.
-	3. The remote source (example, a Git folder) from which charts are downloaded.
+		1. Whether Iter8 should download the experiment charts folder from a remote URL (example, a GitHub URL), or reuse local charts.
+		2. The parent directory of the charts folder.
+		3. The remote URL (example, a GitHub URL) from which charts are downloaded.
 `
 
 // newLaunchCmd creates the launch command
@@ -39,7 +39,7 @@ func newLaunchCmd(kd *driver.KubeDriver) *cobra.Command {
 	}
 	addDryRunFlag(cmd, &actor.DryRun)
 	addChartsParentDirFlag(cmd, &actor.ChartsParentDir)
-	addFolderFlag(cmd, &actor.Folder)
+	addRemoteFolderURLFlag(cmd, &actor.RemoteFolderURL)
 	addChartNameFlag(cmd, &actor.ChartName)
 	addValueFlags(cmd.Flags(), &actor.Options)
 	addRunDirFlag(cmd, &actor.RunDir)
diff --git a/driver/kubedriver.go b/driver/kubedriver.go
index 540ed22a..fb5580d3 100644
--- a/driver/kubedriver.go
+++ b/driver/kubedriver.go
@@ -281,20 +281,32 @@ func (driver *KubeDriver) formResultSecret(r *base.ExperimentResult) (*corev1.Se
 // createExperimentResultSecret creates the experiment result secret
 func (driver *KubeDriver) createExperimentResultSecret(r *base.ExperimentResult) error {
 	if sec, err := driver.formResultSecret(r); err == nil {
-		secretsClient := driver.Clientset.CoreV1().Secrets(driver.Namespace())
-		_, err1 := secretsClient.Create(context.Background(), sec, metav1.CreateOptions{})
-		// TODO: Evaluate if result secret creation requires retries.
-		// Probably not. A get call precedes creation,
-		// and is retried if there are permission issues.
+		err1 := retry.OnError(
+			wait.Backoff{
+				Steps:    int(secretTimeout / retryInterval),
+				Cap:      secretTimeout,
+				Duration: retryInterval,
+				Factor:   1.0,
+				Jitter:   0.1,
+			},
+			func(err2 error) bool { // retry on specific failures
+				return kerrors.ReasonForError(err2) == metav1.StatusReasonForbidden
+			},
+			func() error {
+				secretsClient := driver.Clientset.CoreV1().Secrets(driver.Namespace())
+				_, err3 := secretsClient.Create(context.Background(), sec, metav1.CreateOptions{})
+				return err3
+			},
+		)
 		if err1 != nil {
-			err2 := fmt.Errorf("unable to create secret %v", sec.Name)
-			log.Logger.WithStackTrace(err1.Error()).Error(err2)
-			return err2
+			err4 := fmt.Errorf("unable to create secret %v", sec.Name)
+			log.Logger.WithStackTrace(err1.Error()).Error(err4)
+			return err4
 		}
+		return nil
 	} else {
 		return err
 	}
-	return nil
 }
 
 // updateExperimentResultSecret updates the experiment result secret


Test patch:
diff --git a/action/hub_test.go b/action/hub_test.go
index f488ca6d..f59884e0 100644
--- a/action/hub_test.go
+++ b/action/hub_test.go
@@ -11,7 +11,7 @@ func TestHub(t *testing.T) {
 	// fix hOpts
 	hOpts := NewHubOpts()
 	os.Chdir(t.TempDir())
-	hOpts.Folder = "github.com/iter8-tools/iter8.git//charts"
+	hOpts.RemoteFolderURL = "github.com/iter8-tools/iter8.git//charts"
 
 	err := hOpts.LocalRun()
 	assert.NoError(t, err)
diff --git a/action/launch_test.go b/action/launch_test.go
index 0b0e2b1c..5d1f457e 100644
--- a/action/launch_test.go
+++ b/action/launch_test.go
@@ -33,7 +33,7 @@ func TestKubeLaunch(t *testing.T) {
 	lOpts.ChartName = "load-test-http"
 	lOpts.Values = []string{"url=https://httpbin.org/get", "duration=2s"}
 	// fixing git ref forever
-	lOpts.Folder = defaultIter8Repo + "?ref=v0.10.6" + "//" + chartsFolderName
+	lOpts.RemoteFolderURL = defaultIter8Repo + "?ref=v0.10.6" + "//" + chartsFolderName
 
 	os.Chdir(t.TempDir())
 	err = lOpts.KubeRun()
diff --git a/cmd/hub_test.go b/cmd/hub_test.go
index 0f587de0..6c9c7c0e 100644
--- a/cmd/hub_test.go
+++ b/cmd/hub_test.go
@@ -12,7 +12,7 @@ func TestHub(t *testing.T) {
 		// basic hub
 		{
 			name:   "basic hub",
-			cmd:    "hub --folder github.com/iter8-tools/iter8.git//charts",
+			cmd:    "hub --remoteFolderURL github.com/iter8-tools/iter8.git//charts",
 			golden: base.CompletePath("../testdata", "output/hub.txt"),
 		},
 	}