Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make custom console respect custom params #1250

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 66 additions & 26 deletions docs/content/docs/install/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,55 +186,95 @@ A few settings are available to configure this feature:

`<filename>`, `<line>`, `<error>`

### Reporting logs URL to Tekton Dashboard or a custom Dashboard or Console
### Reporting logs

Pipelines as Code have the ability to automatically detect the OpenShift Console and link the logs of the tasks to the
public URL of the OpenShift Console. If you are using the Tekton Dashboard, you can configure this feature using the
`tekton-dashboard-url` setting. Simply set this to your dashboard URL, and the pipelinerun status and tasklog will be
displayed there.
Pipeline as Code can report the logs of the tasks to the [OpenShift
Console](https://docs.openshift.com/container-platform/latest/web_console/web-console.html),
the [Tekton Dashboard](https://tekton.dev/docs/dashboard/) or if you have your
own give you flexibility to link to your custom Dashboard.

Alternatively, you can also configure a custom console with the following settings:
#### OpenShift Console

Pipelines as Code will automatically detect the OpenShift Console and link the logs of the tasks to the
public URL of the OpenShift Console.

#### [Tekton Dashboard](https://tekton.dev/docs/dashboard/)

If you are using the Tekton Dashboard, you can configure this feature using the
`tekton-dashboard-url` setting. Simply set this to your dashboard URL, and the pipelinerun status and tasklog will be
displayed there.

#### Custom Console (or dashboard)

Alternatively, you have the ability to configure the links to go to your custom
dashboard using the following settings:

* `custom-console-name`

Set this to the name of your custom console. example: `MyCorp Console`
Set this to the name of your custom console. example: `MyCorp Console`

* `custom-console-url`

Set this to the root URL of your custom console. example: `https://mycorp.com`
Set this to the root URL of your custom console. example: `https://mycorp.com`

* `custom-console-url-pr-details`

Set this to the URL where to view the details of the `PipelineRun`. This is
shown when the PipelineRun is started so the user can follow execution on your
console or when to see more details about the pipelinerun ion result.
Set this to the URL where to view the details of the `PipelineRun`. This is
shown when the PipelineRun is started so the user can follow execution on your
console or when to see more details about the pipelinerun on result.

The URL suports templating for these value:
The URL suports all the standard variables as exposed on the Pipelinerun (refer to
the documentation on [Authoring PipelineRuns](../authoringprs)) with the added
variable:

* `{{ namespace }}`: The target namespace where the pipelinerun is executed
* `{{ pr }}`: The PipelineRun name.
* `{{ namespace }}`: The target namespace where the pipelinerun is executed
* `{{ pr }}`: The PipelineRun name.

example: `https://mycorp.com/ns/{{ namespace }}/pipelinerun/{{ pr }}`

example: `https://mycorp.com/ns/{{ namespace }}/pipelienrun/{{ pr }}`
Moreover it can access the [custom parameters](../guide/repositorycrd/#custom-parameter-expansion) from a
Repository CR. For example if the user has a parameter in their Repo CR like this :

* `custom-console-url-pr-tasklog`
```yaml
[...]
spec:
params:
- name: custom
value: value
```

Set this to the URL where to view the log of the taskrun of the `PipelineRun`. This is
shown when we post a result of the task breakdown to link to the logs of the taskrun.
and the global configuration setting for `custom-console-url-pr-details` is:

`https://mycorp.com/ns/{{ namespace }}/{{ custom }}`

the `{{ custom }}` tag in the URL is expanded as `value`.

This let operator to add specific informations like a `UUID` about a user as
parameter in their repo CR and let it link to the console.

* `custom-console-url-pr-tasklog`

Set this to the URL where to view the log of the taskrun of the `PipelineRun`. This is
shown when we post a result of the task breakdown to link to the logs of the taskrun.

The URL suports templating for these value:
The URL suports custom parameter on Repo CR and the standard parameters as
described in the `custom-console-url-pr-details` setting and as well those added
values:

* `{{ namespace }}`: The target namespace where the pipelinerun is executed
* `{{ pr }}`: The PipelineRun name.
* `{{ task }}`: The Task name in the PR
* `{{ pod }}`: The Pod name of the TaskRun
* `{{ firstFailedStep }}`: The name of the first failed step in the TaskRun
* `{{ namespace }}`: The target namespace where the pipelinerun is executed
* `{{ pr }}`: The PipelineRun name.
* `{{ task }}`: The Task name in the PR
* `{{ pod }}`: The Pod name of the TaskRun
* `{{ firstFailedStep }}`: The name of the first failed step in the TaskRun

example: `https://mycorp.com/ns/{{ namespace }}/pipelinerun/{{ pr }}/logs/{{ task }}#{{ pod }}-{{ firstFailedStep }}`
example: `https://mycorp.com/ns/{{ namespace }}/pipelinerun/{{ pr }}/logs/{{ task }}#{{ pod }}-{{ firstFailedStep }}`

## Pipelines-As-Code Info

There are a settings exposed through a config map for which any authenticated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove "a"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also remove "for"

user can access to know about the Pipeline as Code status.
user can access to know about the Pipeline as Code status. This Configmap
will be automatically created with the [OpenShift Pipelines Operator](https://docs.openshift.com/container-platform/latest/cicd/pipelines/understanding-openshift-pipelines.html)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice to mention the name of the ConfigMap, seeing as the user might want to access it?

or when installing with [tkn pac boostrap](../../guide/cli/#bootstrap) command.

* `version`

Expand Down
8 changes: 7 additions & 1 deletion pkg/apis/pipelinesascode/keys/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ limitations under the License.

package keys

import "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode"
import (
"regexp"

"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode"
)

const (
Task = pipelinesascode.GroupName + "/task"
Expand Down Expand Up @@ -55,3 +59,5 @@ const (
GithubApplicationID = "github-application-id"
GithubPrivateKey = "github-private-key"
)

var ParamsRe = regexp.MustCompile(`{{([^}]{2,})}}`)
51 changes: 39 additions & 12 deletions pkg/consoleui/custom.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package consoleui
import (
"context"
"fmt"
"net/url"
"strings"

"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/settings"
"github.com/openshift-pipelines/pipelines-as-code/pkg/templates"
Expand All @@ -12,7 +15,12 @@ import (
)

type CustomConsole struct {
Info *info.Info
Info *info.Info
params map[string]string
}

func (o *CustomConsole) SetParams(mt map[string]string) {
o.params = mt
}

func (o *CustomConsole) GetName() string {
Expand All @@ -29,14 +37,32 @@ func (o *CustomConsole) URL() string {
return o.Info.Pac.CustomConsoleURL
}

// generateURL will generate a URL from a template, trim some of the spaces and
// \n we get from yaml
// return the default URL if there it's not become a proper url or that it has
// some of the templates like {{}} left
func (o *CustomConsole) generateURL(urlTmpl string, dict map[string]string) string {
newurl := templates.ReplacePlaceHoldersVariables(urlTmpl, dict)
// trim new line because yaml parser adds new line at the end of the string
newurl = strings.TrimSpace(strings.TrimSuffix(newurl, "\n"))
if _, err := url.ParseRequestURI(newurl); err != nil {
return o.URL()
}
// detect if there is still some {{}} in the url
if keys.ParamsRe.MatchString(newurl) {
return o.URL()
}
return newurl
}

func (o *CustomConsole) DetailURL(pr *tektonv1.PipelineRun) string {
if o.Info.Pac.CustomConsolePRdetail == "" {
return fmt.Sprintf("https://detailurl.setting.%s.is.not.configured", settings.CustomConsolePRDetailKey)
}
return templates.ReplacePlaceHoldersVariables(o.Info.Pac.CustomConsolePRdetail, map[string]string{
"namespace": pr.GetNamespace(),
"pr": pr.GetName(),
})
nm := o.params
nm["namespace"] = pr.GetNamespace()
nm["pr"] = pr.GetName()
return o.generateURL(o.Info.Pac.CustomConsolePRdetail, nm)
}

func (o *CustomConsole) TaskLogURL(pr *tektonv1.PipelineRun, taskRunStatus *tektonv1.PipelineRunTaskRunStatus) string {
Expand All @@ -51,13 +77,14 @@ func (o *CustomConsole) TaskLogURL(pr *tektonv1.PipelineRun, taskRunStatus *tekt
break
}
}
return templates.ReplacePlaceHoldersVariables(o.Info.Pac.CustomConsolePRTaskLog, map[string]string{
"namespace": pr.GetNamespace(),
"pr": pr.GetName(),
"task": taskRunStatus.PipelineTaskName,
"pod": taskRunStatus.Status.PodName,
"firstFailedStep": firstFailedStep,
})

nm := o.params
nm["namespace"] = pr.GetNamespace()
nm["pr"] = pr.GetName()
nm["task"] = taskRunStatus.PipelineTaskName
nm["pod"] = taskRunStatus.Status.PodName
nm["firstFailedStep"] = firstFailedStep
return o.generateURL(o.Info.Pac.CustomConsolePRTaskLog, nm)
}

func (o *CustomConsole) UI(_ context.Context, _ dynamic.Interface) error {
Expand Down
29 changes: 25 additions & 4 deletions pkg/consoleui/custom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
func TestCustomGood(t *testing.T) {
consoleName := "MyCorp Console"
consoleURL := "https://mycorp.console"
consolePRdetail := "https://mycorp.console/{{ namespace }}/{{ pr }}"
consolePRtasklog := "https://mycorp.console/{{ namespace }}/{{ pr }}/{{ task }}/{{ pod }}/{{ firstFailedStep }}"
consolePRdetail := "https://mycorp.console/{{ namespace }}/{{ pr }}/params/{{ foo }}"
consolePRtasklog := "https://mycorp.console/{{ namespace }}/{{ pr }}/{{ task }}/{{ pod }}/{{ firstFailedStep }}/params/{{ foo }}/{{ nonewline }}"

c := CustomConsole{
Info: &info.Info{
Expand All @@ -30,6 +30,10 @@ func TestCustomGood(t *testing.T) {
},
},
}
c.SetParams(map[string]string{
"foo": "bar",
"nonewline": "nonewline\n ",
})
pr := &tektonv1.PipelineRun{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns",
Expand Down Expand Up @@ -64,8 +68,25 @@ func TestCustomGood(t *testing.T) {
}
assert.Equal(t, c.GetName(), consoleName)
assert.Equal(t, c.URL(), consoleURL)
assert.Equal(t, c.DetailURL(pr), "https://mycorp.console/ns/pr")
assert.Equal(t, c.TaskLogURL(pr, trStatus), "https://mycorp.console/ns/pr/task/pod/failure")
assert.Equal(t, c.DetailURL(pr), "https://mycorp.console/ns/pr/params/bar")
assert.Equal(t, c.TaskLogURL(pr, trStatus), "https://mycorp.console/ns/pr/task/pod/failure/params/bar/nonewline")

// test if we fallback properly
f := CustomConsole{
Info: &info.Info{
Pac: &info.PacOpts{
Settings: &settings.Settings{
CustomConsoleName: consoleName,
CustomConsoleURL: consoleURL,
CustomConsolePRdetail: "{{ notthere}}",
CustomConsolePRTaskLog: "{{ notthere}}",
},
},
},
}
f.SetParams(map[string]string{})
assert.Assert(t, strings.Contains(c.DetailURL(pr), consoleURL))
assert.Assert(t, strings.Contains(c.TaskLogURL(pr, trStatus), consoleURL))
}

func TestCustomBad(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions pkg/consoleui/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Interface interface {
UI(ctx context.Context, kdyn dynamic.Interface) error
URL() string
GetName() string
SetParams(mt map[string]string)
}

type FallBackConsole struct{}
Expand All @@ -40,6 +41,9 @@ func (f FallBackConsole) URL() string {
return consoleIsnotConfiguredURL
}

func (f FallBackConsole) SetParams(_ map[string]string) {
}

func New(ctx context.Context, kdyn dynamic.Interface, _ *info.Info) Interface {
oc := &OpenshiftConsole{}
if err := oc.UI(ctx, kdyn); err == nil {
Expand Down
3 changes: 3 additions & 0 deletions pkg/consoleui/openshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ type OpenshiftConsole struct {
host string
}

func (o *OpenshiftConsole) SetParams(_ map[string]string) {
}

func (o *OpenshiftConsole) GetName() string {
return openshiftConsoleName
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/consoleui/tektondashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ func (t *TektonDashboard) URL() string {
func (t *TektonDashboard) UI(_ context.Context, _ dynamic.Interface) error {
return nil
}

func (t *TektonDashboard) SetParams(_ map[string]string) {
}
74 changes: 74 additions & 0 deletions pkg/customparams/cel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package customparams
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add test for cel as well ?


import (
"encoding/json"
"fmt"

"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
)

func celEvaluate(expr string, env *cel.Env, data map[string]interface{}) (ref.Val, error) {
parsed, issues := env.Parse(expr)
if issues != nil && issues.Err() != nil {
return nil, fmt.Errorf("failed to parse expression %#v: %w", expr, issues.Err())
}

checked, issues := env.Check(parsed)
if issues != nil && issues.Err() != nil {
return nil, fmt.Errorf("expression %#v check failed: %w", expr, issues.Err())
}

prg, err := env.Program(checked, cel.EvalOptions(cel.OptOptimize))
if err != nil {
return nil, fmt.Errorf("expression %#v failed to create a Program: %w", expr, err)
}

out, _, err := prg.Eval(data)
if err != nil {
return nil, fmt.Errorf("expression %#v failed to evaluate: %w", expr, err)
}

return out, nil
}

// celFilter filters a query with cel, it sets two variables for the user to use
// in the cel filter. body and pac.
// body is the payload of the event, since it's already casted we un-marshall/and
// marshall it as a map[string]interface{}
func celFilter(query string, body any, pacParams map[string]string) (bool, error) {
nbody, err := json.Marshal(body)
if err != nil {
return false, err
}
var jsonMap map[string]interface{}
err = json.Unmarshal(nbody, &jsonMap)
if err != nil {
return false, err
}

mapStrDyn := decls.NewMapType(decls.String, decls.Dyn)
celDec, _ := cel.NewEnv(
cel.Declarations(
decls.NewVar("body", mapStrDyn),
decls.NewVar("pac", mapStrDyn),
))
val, err := celEvaluate(query, celDec, map[string]any{
"body": jsonMap,
"pac": pacParams,
})
if err != nil {
return false, err
}

//nolint: gocritic
switch val.(type) {
case types.Bool:
if val.Value() == true {
return true, nil
}
}
return false, nil
}
Loading