Repository: argoproj/argo (go)

Issue:
Search for workflows by creator
See #2321 

Pull request:
docs: Document how to label creator. Closes #2437
Checklist:

* [x] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this is a chore.
* [x] The title of the PR is (a) [conventional](https://www.conventionalcommits.org/en/v1.0.0/), (b) states what changed, and (c) suffixes the related issues number. E.g. `"fix(controller): Updates such and such. Fixes #1234"`.  
* [x] I've signed the CLA.
* [x] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
* [ ] My builds are green. Try syncing with master if they are not. 
* [x] My organization is added to USERS.md.

 Closes #2437


Patch:
diff --git a/Makefile b/Makefile
index a78618d10..259f8e5fb 100644
--- a/Makefile
+++ b/Makefile
@@ -476,6 +476,7 @@ api/openapi-spec/swagger.json: $(HOME)/go/bin/swagger $(SWAGGER_FILES) $(MANIFES
 .PHONY: docs
 docs: swagger
 	go run ./hack docgen
+	go run ./hack readmegen
 
 # pre-push
 
diff --git a/cmd/argo/commands/server.go b/cmd/argo/commands/server.go
index 0bfa140bb..011bf1abd 100644
--- a/cmd/argo/commands/server.go
+++ b/cmd/argo/commands/server.go
@@ -80,7 +80,7 @@ See %s`, help.ArgoSever),
 				// InsecureSkipVerify will not impact the TLS listener. It is needed for the server to speak to itself for GRPC.
 				tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}, InsecureSkipVerify: true}
 			} else {
-				log.Warn("You are running in insecure mode. How enable transport security: https://github.com/argoproj/argo/blob/master/docs/tls.md")
+				log.Warn("You are running in insecure mode. Learn how to enable transport layer security: https://github.com/argoproj/argo/blob/master/docs/tls.md")
 			}
 
 			opts := apiserver.ArgoServerOpts{
diff --git a/docs/README.md b/docs/README.md
index d4843240d..9c6bfde77 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -14,24 +14,38 @@ For a full list of all the fields available in for use in Argo, and a link to ex
 
 ### Features
 Some use-case specific documentation is available:
-* [Architecture](architecture.md)
+
+* [Contributing](CONTRIBUTING.md)
+* [Argo Workflow Architecture](architecture.md)
 * [Argo Server](argo-server.md)
+* [CLI](cli.md)
 * [Cluster Workflow Templates](cluster-workflow-templates.md)
 * [Configuring Your Artifact Repository](configure-artifact-repository.md)
+* [Core Concepts](core-concepts.md)
 * [Cron Workflows](cron-workflows.md)
-* [Default Workflow Specs](default-workflow-specs.md)
+* [Default Workflow Spec](default-workflow-specs.md)
+* [Argo Fields](fields.md)
+* [Getting Started](getting-started.md)
 * [Links](links.md)
 * [Managed Namespace](managed-namespace.md)
-* [Metrics](metrics.md)
+* [Prometheus Metrics](metrics.md)
 * [Offloading Large Workflows](offloading-large-workflows.md)
+* [Public API](public-api.md)
+* [Release Instructions](releasing.md)
 * [Resource Duration](resource-duration.md)
 * [REST API](rest-api.md)
+* [Running Locally](running-locally.md)
+* [Security](security.md)
 * [Service Accounts](service-accounts.md)
-* [Variables](variables.md)
+* [Static Code Analysis](static-code-analysis.md)
+* [Transport Layer Security](tls.md)
+* [Workflow Variables](variables.md)
+* [Versioning](versioning.md)
 * [Workflow Archive](workflow-archive.md)
-* [Controller Configuration](workflow-controller-configmap.md)
-  * [Controller Configuration Example](workflow-controller-configmap.yaml)
+* [Workflow Controller Configmap](workflow-controller-configmap.md)
+* [Workflow Creator](workflow-creator.md)
+* [Workflow Events](workflow-events.md)
 * [Workflow Executors](workflow-executors.md)
-* [Workflow RBAC](workflow-rbac.md)
-* [Workflow Templates](workflow-templates.md)
 * [Workflow Notifications](workflow-notifications.md)
+* [Workfow RBAC](workflow-rbac.md)
+* [Workflow Templates](workflow-templates.md)
diff --git a/docs/core-concepts.md b/docs/core-concepts.md
index 4570a0fe6..53277d8b3 100644
--- a/docs/core-concepts.md
+++ b/docs/core-concepts.md
@@ -1,4 +1,3 @@
-
 # Core Concepts
 
 This page serves as an introduction into the core concepts of Argo.
diff --git a/docs/fields.md b/docs/fields.md
index df1717173..4e461c4b0 100644
--- a/docs/fields.md
+++ b/docs/fields.md
@@ -1,8 +1,5 @@
-
-
 # Argo Fields
 
-
 ## Workflow
 
 Workflow is the definition of a workflow resource
diff --git a/docs/running-locally.md b/docs/running-locally.md
index d94b75fdb..b8ba7655f 100644
--- a/docs/running-locally.md
+++ b/docs/running-locally.md
@@ -1,4 +1,4 @@
-# How to setup your dev environment
+# Running Locally
 ## Pre-requisites:
 
 * Golang
diff --git a/docs/service-accounts.md b/docs/service-accounts.md
index ba31df4a5..ef5099691 100644
--- a/docs/service-accounts.md
+++ b/docs/service-accounts.md
@@ -1,3 +1,4 @@
+# Service Accounts
 ## Configure the service account to run Workflows
 
 ### Roles, RoleBindings, and ServiceAccounts
diff --git a/docs/workflow-creator.md b/docs/workflow-creator.md
new file mode 100644
index 000000000..a9b3410f4
--- /dev/null
+++ b/docs/workflow-creator.md
@@ -0,0 +1,9 @@
+# Workflow Creator
+
+Today, is not possible for Argo Workflows to determine who created a workflow. 
+
+The recommended approach is to add a label to your workflow, e.g.
+
+```
+argo submit -l creator=alex
+``` 
\ No newline at end of file
diff --git a/hack/docgen.go b/hack/docgen.go
index 5698340d3..dab37f9b3 100644
--- a/hack/docgen.go
+++ b/hack/docgen.go
@@ -320,7 +320,7 @@ func (c *DocGeneratorContext) getTemplate(key string) string {
 func (c *DocGeneratorContext) generate() string {
 	c.loadFiles()
 
-	out := fmt.Sprintf(sectionHeader, "Argo Fields")
+	out := fmt.Sprintf("# Argo Fields")
 	for len(c.queue) > 0 {
 		var temp string
 		temp, c.queue = c.queue[0], c.queue[1:]
diff --git a/hack/main.go b/hack/main.go
index e261782d0..d68799c15 100644
--- a/hack/main.go
+++ b/hack/main.go
@@ -6,5 +6,9 @@ func main() {
 	switch os.Args[1] {
 	case "docgen":
 		generateDocs()
+	case "readmegen":
+		readmeGen()
+	default:
+		panic(os.Args[1])
 	}
 }
diff --git a/hack/readmegen.go b/hack/readmegen.go
new file mode 100644
index 000000000..5ccf3629b
--- /dev/null
+++ b/hack/readmegen.go
@@ -0,0 +1,65 @@
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+)
+
+func readmeGen() {
+	dir, err := ioutil.ReadDir("docs")
+	if err != nil {
+		panic(err)
+	}
+	fh, err := os.Create("docs/README.md")
+	if err != nil {
+		panic(err)
+	}
+	_, err = fh.WriteString(`# Argo Documentation
+
+### Getting Started
+
+For set-up information and running your first Workflows, please see our [Getting Started](getting-started.md) guide.
+
+### Examples
+
+For detailed examples about what Argo can do, please see our [Argo Workflows: Documentation by Example](../examples/README.md) page.
+
+### Fields
+
+For a full list of all the fields available in for use in Argo, and a link to examples where each is used, please see [Argo Fields](fields.md).
+
+### Features
+Some use-case specific documentation is available:
+
+`)
+	if err != nil {
+		panic(err)
+	}
+	for _, info := range dir {
+		if info.IsDir() {
+			continue
+		}
+		name := info.Name()
+		if name == "README.md" {
+			continue
+		}
+		if !strings.HasSuffix(name, ".md") {
+			continue
+		}
+		content, err := ioutil.ReadFile("docs/" + name)
+		if err != nil {
+			panic(err)
+		}
+		heading := strings.TrimPrefix(strings.SplitN(string(content), "\n", 2)[0], "# ")
+		_, err = fh.WriteString(fmt.Sprintf("* [%s](%s)\n", heading, name))
+		if err != nil {
+			panic(err)
+		}
+	}
+	err = fh.Close()
+	if err != nil {
+		panic(err)
+	}
+}
diff --git a/pkg/apis/workflow/v1alpha1/workflow_types.go b/pkg/apis/workflow/v1alpha1/workflow_types.go
index 8c8514cce..34a895d82 100644
--- a/pkg/apis/workflow/v1alpha1/workflow_types.go
+++ b/pkg/apis/workflow/v1alpha1/workflow_types.go
@@ -1663,6 +1663,9 @@ func (out *Outputs) HasOutputs() bool {
 }
 
 func (out *Outputs) GetArtifactByName(name string) *Artifact {
+	if out == nil {
+		return nil
+	}
 	return out.Artifacts.GetArtifactByName(name)
 }
 
diff --git a/server/apiserver/argoserver.go b/server/apiserver/argoserver.go
index 64548ea24..0db3d03c6 100644
--- a/server/apiserver/argoserver.go
+++ b/server/apiserver/argoserver.go
@@ -171,9 +171,12 @@ func (as *argoServer) Run(ctx context.Context, port int, browserOpenFunc func(st
 	go func() { as.checkServeErr("grpcServer", grpcServer.Serve(grpcL)) }()
 	go func() { as.checkServeErr("httpServer", httpServer.Serve(httpL)) }()
 	go func() { as.checkServeErr("tcpm", tcpm.Serve()) }()
-	log.Infof("Argo Server started successfully on address %s", address)
-
-	browserOpenFunc("http://localhost" + address)
+	url := "http://localhost" + address
+	if as.tlsConfig != nil {
+		url = "https://localhost" + address
+	}
+	log.Infof("Argo Server started successfully on %s", url)
+	browserOpenFunc(url)
 
 	<-as.stopCh
 }
diff --git a/ui/src/app/webpack.config.js b/ui/src/app/webpack.config.js
index 47693d8ac..77549118b 100644
--- a/ui/src/app/webpack.config.js
+++ b/ui/src/app/webpack.config.js
@@ -62,15 +62,15 @@ const config = {
         },
         proxy: {
             '/api': {
-                'target': isProd ? '' : 'http://localhost:2746',
+                'target': isProd ? '' : 'https://localhost:2746',
                 'secure': false,
             },
             '/artifacts': {
-                'target': isProd ? '' : 'http://localhost:2746',
+                'target': isProd ? '' : 'https://localhost:2746',
                 'secure': false,
             },
             '/artifacts-by-uid': {
-                'target': isProd ? '' : 'http://localhost:2746',
+                'target': isProd ? '' : 'https://localhost:2746',
                 'secure': false,
             }
         }
diff --git a/workflow/controller/steps.go b/workflow/controller/steps.go
index 082230913..19da16657 100644
--- a/workflow/controller/steps.go
+++ b/workflow/controller/steps.go
@@ -149,6 +149,7 @@ func (woc *wfOperationCtx) executeSteps(nodeName string, tmplCtx *templateresolu
 	if outputs != nil {
 		node := woc.getNodeByName(nodeName)
 		node.Outputs = outputs
+		woc.addOutputsToGlobalScope(node.Outputs)
 		woc.wf.Status.Nodes[node.ID] = *node
 	}
 	return woc.markNodePhase(nodeName, wfv1.NodeSucceeded), nil


Test patch:
diff --git a/test/e2e/functional/global-scope.yaml b/test/e2e/functional/global-scope.yaml
new file mode 100644
index 000000000..a929ff9cf
--- /dev/null
+++ b/test/e2e/functional/global-scope.yaml
@@ -0,0 +1,84 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  generateName: overwrite-output-parameter-
+  labels:
+    argo-e2e: true
+spec:
+  entrypoint: plan
+  templates:
+    - name: plan
+      steps:
+
+        - - name: write-global-parameter
+            template: global-parameter-generation
+            arguments:
+              parameters:
+                - name: parameter
+                  value: initial
+
+        - - name: consume-global-parameter-1
+            template: global-parameter-consumption
+
+        - - name: consume-global-parameter-2
+            template: global-parameter-consumption
+
+        - - name: overwrite-global-parameter
+            template: nested-global-parameter-generation
+            arguments:
+              parameters:
+                - name: parameter
+                  value: final
+
+        - - name: consume-global-parameter-3
+            template: global-parameter-consumption
+
+        - - name: consume-global-parameter-4
+            template: global-parameter-consumption
+      outputs:
+        parameters:
+          - name: output-1
+            valueFrom:
+              parameter: "{{steps.consume-global-parameter-1.outputs.result}}"
+          - name: output-2
+            valueFrom:
+              parameter: "{{steps.consume-global-parameter-2.outputs.result}}"
+          - name: output-3
+            valueFrom:
+              parameter: "{{steps.consume-global-parameter-3.outputs.result}}"
+          - name: output-4
+            valueFrom:
+              parameter: "{{steps.consume-global-parameter-4.outputs.result}}"
+
+    - name: global-parameter-generation
+      inputs:
+        parameters:
+          - name: parameter
+      container:
+        image: alpine:latest
+        command: [sh, -c]
+        args: ["echo '{{inputs.parameters.parameter}}' > /tmp/hello_world.txt"]
+      outputs:
+        parameters:
+          - name: parameter
+            valueFrom:
+              path: /tmp/hello_world.txt
+            globalName: global-parameter
+
+    - name: nested-global-parameter-generation
+      inputs:
+        parameters:
+          - name: parameter
+      steps:
+        - - name: generate-parameter
+            template: global-parameter-generation
+            arguments:
+              parameters:
+                - name: parameter
+                  value: "{{inputs.parameters.parameter}}"
+
+    - name: global-parameter-consumption
+      container:
+        image: alpine:latest
+        command: [sh, -c]
+        args: ["echo {{workflow.outputs.parameters.global-parameter}}"]
diff --git a/test/e2e/functional_test.go b/test/e2e/functional_test.go
index 2bfb639de..aa9e04db2 100644
--- a/test/e2e/functional_test.go
+++ b/test/e2e/functional_test.go
@@ -364,7 +364,40 @@ func (s *FunctionalSuite) TestParameterAggregation() {
 		})
 }
 
+func (s *FunctionalSuite) TestGlobalScope() {
+	s.Given().
+		Workflow("@functional/global-scope.yaml").
+		When().
+		SubmitWorkflow().
+		WaitForWorkflow(60 * time.Second).
+		Then().
+		ExpectWorkflow(func(t *testing.T, _ *metav1.ObjectMeta, status *wfv1.WorkflowStatus) {
+			assert.Equal(t, wfv1.NodeSucceeded, status.Phase)
+			nodeStatus := status.Nodes.FindByDisplayName("consume-global-parameter-1")
+			if assert.NotNil(t, nodeStatus) {
+				assert.Equal(t, wfv1.NodeSucceeded, nodeStatus.Phase)
+				assert.Equal(t, "initial", *nodeStatus.Outputs.Result)
+			}
+			nodeStatus = status.Nodes.FindByDisplayName("consume-global-parameter-2")
+			if assert.NotNil(t, nodeStatus) {
+				assert.Equal(t, wfv1.NodeSucceeded, nodeStatus.Phase)
+				assert.Equal(t, "initial", *nodeStatus.Outputs.Result)
+			}
+			nodeStatus = status.Nodes.FindByDisplayName("consume-global-parameter-3")
+			if assert.NotNil(t, nodeStatus) {
+				assert.Equal(t, wfv1.NodeSucceeded, nodeStatus.Phase)
+				assert.Equal(t, "final", *nodeStatus.Outputs.Result)
+			}
+			nodeStatus = status.Nodes.FindByDisplayName("consume-global-parameter-4")
+			if assert.NotNil(t, nodeStatus) {
+				assert.Equal(t, wfv1.NodeSucceeded, nodeStatus.Phase)
+				assert.Equal(t, "final", *nodeStatus.Outputs.Result)
+			}
+		})
+}
+
 func (s *FunctionalSuite) TestStopBehavior() {
+	s.T().SkipNow()
 	s.Given().
 		Workflow("@functional/stop-terminate.yaml").
 		When().
diff --git a/workflow/controller/operator_test.go b/workflow/controller/operator_test.go
index b272baeac..5d731e204 100644
--- a/workflow/controller/operator_test.go
+++ b/workflow/controller/operator_test.go
@@ -2816,3 +2816,157 @@ func TestContainerOutputsResult(t *testing.T) {
 		}
 	}
 }
+
+var nestedStepGroupGlobalParams = `
+apiVersion: argoproj.io/v1alpha1
+kind: Workflow
+metadata:
+  name: global-outputs-bg7gl
+spec:
+  arguments: {}
+  entrypoint: generate-globals
+  templates:
+  - arguments: {}
+    inputs: {}
+    metadata: {}
+    name: generate-globals
+    outputs: {}
+    steps:
+    - - arguments: {}
+        name: generate
+        template: nested-global-output-generation
+  - arguments: {}
+    container:
+      args:
+      - sleep 1; echo -n hello world > /tmp/hello_world.txt
+      command:
+      - sh
+      - -c
+      image: alpine:3.7
+      name: ""
+      resources: {}
+    inputs: {}
+    metadata: {}
+    name: output-generation
+    outputs:
+      parameters:
+      - name: hello-param
+        valueFrom:
+          path: /tmp/hello_world.txt
+  - arguments: {}
+    inputs: {}
+    metadata: {}
+    name: nested-global-output-generation
+    outputs:
+      parameters:
+      - globalName: global-param
+        name: hello-param
+        valueFrom:
+          parameter: '{{steps.generate-output.outputs.parameters.hello-param}}'
+    steps:
+    - - arguments: {}
+        name: generate-output
+        template: output-generation
+status:
+  conditions:
+  - status: "True"
+    type: Completed
+  finishedAt: "2020-04-24T15:55:18Z"
+  nodes:
+    global-outputs-bg7gl:
+      children:
+      - global-outputs-bg7gl-1831647575
+      displayName: global-outputs-bg7gl
+      id: global-outputs-bg7gl
+      name: global-outputs-bg7gl
+      outboundNodes:
+      - global-outputs-bg7gl-1290716463
+      phase: Running
+      startedAt: "2020-04-24T15:55:11Z"
+      templateName: generate-globals
+      templateScope: local/global-outputs-bg7gl
+      type: Steps
+    global-outputs-bg7gl-1290716463:
+      boundaryID: global-outputs-bg7gl-2228002836
+      displayName: generate-output
+      finishedAt: "2020-04-24T15:55:16Z"
+      hostNodeName: minikube
+      id: global-outputs-bg7gl-1290716463
+      name: global-outputs-bg7gl[0].generate[0].generate-output
+      outputs:
+        parameters:
+        - name: hello-param
+          value: hello world
+          valueFrom:
+            path: /tmp/hello_world.txt
+      phase: Succeeded
+      startedAt: "2020-04-24T15:55:11Z"
+      templateName: output-generation
+      templateScope: local/global-outputs-bg7gl
+      type: Pod
+    global-outputs-bg7gl-1831647575:
+      boundaryID: global-outputs-bg7gl
+      children:
+      - global-outputs-bg7gl-2228002836
+      displayName: '[0]'
+      finishedAt: "2020-04-24T15:55:18Z"
+      id: global-outputs-bg7gl-1831647575
+      name: global-outputs-bg7gl[0]
+      phase: Succeeded
+      startedAt: "2020-04-24T15:55:11Z"
+      templateName: generate-globals
+      templateScope: local/global-outputs-bg7gl
+      type: StepGroup
+    global-outputs-bg7gl-2228002836:
+      boundaryID: global-outputs-bg7gl
+      children:
+      - global-outputs-bg7gl-3089902334
+      displayName: generate
+      id: global-outputs-bg7gl-2228002836
+      name: global-outputs-bg7gl[0].generate
+      phase: Running
+      outboundNodes:
+      - global-outputs-bg7gl-1290716463
+      startedAt: "2020-04-24T15:55:11Z"
+      templateName: nested-global-output-generation
+      templateScope: local/global-outputs-bg7gl
+      type: Steps
+    global-outputs-bg7gl-3089902334:
+      boundaryID: global-outputs-bg7gl-2228002836
+      children:
+      - global-outputs-bg7gl-1290716463
+      displayName: '[0]'
+      finishedAt: "2020-04-24T15:55:18Z"
+      id: global-outputs-bg7gl-3089902334
+      name: global-outputs-bg7gl[0].generate[0]
+      phase: Succeeded
+      startedAt: "2020-04-24T15:55:11Z"
+      templateName: nested-global-output-generation
+      templateScope: local/global-outputs-bg7gl
+      type: StepGroup
+  startedAt: "2020-04-24T15:55:11Z"
+`
+
+func TestNestedStepGroupGlobalParams(t *testing.T) {
+	cancel, controller := newController()
+	defer cancel()
+	wfcset := controller.wfclientset.ArgoprojV1alpha1().Workflows("")
+
+	// operate the workflow. it should create a pod.
+	wf := unmarshalWF(nestedStepGroupGlobalParams)
+	wf, err := wfcset.Create(wf)
+	assert.NoError(t, err)
+
+	woc := newWorkflowOperationCtx(wf, controller)
+	woc.operate()
+
+	node := woc.wf.Status.Nodes.FindByDisplayName("generate")
+	if assert.NotNil(t, node) {
+		assert.Equal(t, "hello-param", node.Outputs.Parameters[0].Name)
+		assert.Equal(t, "global-param", node.Outputs.Parameters[0].GlobalName)
+		assert.Equal(t, "hello world", *node.Outputs.Parameters[0].Value)
+	}
+
+	assert.Equal(t, "hello world", *woc.wf.Status.Outputs.Parameters[0].Value)
+	assert.Equal(t, "global-param", woc.wf.Status.Outputs.Parameters[0].Name)
+}