Skip to content

Commit cf22b58

Browse files
committed
Add information on 'tkn pac info' command for Pipelines as Code installation
This commit adds the 'tkn pac info' command, which provides useful information about the installation of Pipelines as Code. The command displays the location and version of the installation, as well as an overview of all the Repositories CR created on the cluster and their associated URLs. It also includes details of any installed GitHub Apps. https://issues.redhat.com/browse/SRVKP-2977 Signed-off-by: Chmouel Boudjnah <[email protected]>
1 parent 8eeca3f commit cf22b58

File tree

18 files changed

+439
-53
lines changed

18 files changed

+439
-53
lines changed

docs/content/docs/guide/cli.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Pipelines as Code provide a powerful CLI designed to work as a plug-in to the [T
1717
* `describe`: describe a Pipelines as Code Repository and the runs associated with it.
1818
* `resolve`: Resolve a pipelinerun as if it were executed by pipelines as code on service.
1919
* `webhook`: Updates webhook secret.
20+
* `info`: Show informations (currently only about your installation with `info install`).
2021

2122
## Install
2223

@@ -317,6 +318,26 @@ There is no clean-up of the secret after the run.
317318

318319
{{< /details >}}
319320

321+
{{< details "tkn pac info install" >}}
322+
323+
### Installation Info
324+
325+
The command tkn pac info provides information about your Pipelines as Code
326+
installation, including the location and version. You can also view an overview
327+
of all Repositories CR created on the cluster and their associated URLs.
328+
329+
If your have your installation set-up with a [GitHub App](../../install/github_apps),
330+
you will be able to the see details of the installed application along with
331+
other relevant information. By default, this will display information from the
332+
public Github API, but you can specify a custom Github API URL using the
333+
`--github-api-url` argument.
334+
335+
It's important to note that only administrators with permission to read the
336+
`pipelines-as-code-secret` secret and list all Repository CR on the cluster are
337+
authorized to access this command.
338+
339+
{{< /details >}}
340+
320341
## Screenshot
321342

322343
![tkn-plug-in](/images/tkn-pac-cli.png)

pkg/adapter/incoming.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/subtle"
66
"fmt"
77
"net/http"
8+
"os"
89

910
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
1011
"github.com/openshift-pipelines/pipelines-as-code/pkg/formatting"
@@ -71,7 +72,10 @@ func (l *listener) detectIncoming(ctx context.Context, req *http.Request, payloa
7172

7273
if repo.Spec.GitProvider == nil || repo.Spec.GitProvider.Type == "" {
7374
gh := github.New()
74-
enterpriseURL, token, installationID, err := app.GetAndUpdateInstallationID(ctx, req, l.run, repo, gh)
75+
76+
// TODO: move this out of here
77+
ns := os.Getenv("SYSTEM_NAMESPACE")
78+
enterpriseURL, token, installationID, err := app.GetAndUpdateInstallationID(ctx, req, l.run, repo, gh, ns)
7579
if err != nil {
7680
return false, nil, err
7781
}

pkg/apis/pipelinesascode/keys/keys.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,7 @@ const (
5151
// default is "https://api.github.com" but it can be overridden by X-GitHub-Enterprise-Host header
5252
PublicGithubAPIURL = "https://api.github.com"
5353
// installationURL give us the Installation ID
54-
InstallationURL = "/app/installations"
54+
InstallationURL = "/app/installations"
55+
GithubApplicationID = "github-application-id"
56+
GithubPrivateKey = "github-private-key"
5557
)

pkg/cmd/tknpac/describe/describe.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,7 @@ func Root(run *params.Run, ioStreams *cli.IOStreams) *cobra.Command {
112112

113113
ctx := context.Background()
114114
clock := clockwork.NewRealClock()
115-
err = run.Clients.NewClients(ctx, &run.Info)
116-
if err != nil {
115+
if err := run.Clients.NewClients(ctx, &run.Info); err != nil {
117116
return err
118117
}
119118

pkg/cmd/tknpac/info/install.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package info
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"text/tabwriter"
9+
"text/template"
10+
11+
_ "embed"
12+
13+
"github.com/google/go-github/v50/github"
14+
"github.com/juju/ansiterm"
15+
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
16+
"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
17+
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
18+
"github.com/openshift-pipelines/pipelines-as-code/pkg/provider/github/app"
19+
"github.com/spf13/cobra"
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
var targetNamespaces = []string{"openshift-pipelines", "pipelines-as-code"}
24+
25+
// google/go-github is missing the count from their struct
26+
type GithubApp struct {
27+
*github.App
28+
InstallationsCount int `json:"installations_count,omitempty"`
29+
}
30+
31+
//go:embed templates/info.tmpl
32+
var infoTemplate string
33+
34+
func getPacLocation(ctx context.Context, run *params.Run) (string, string, error) {
35+
for _, ns := range targetNamespaces {
36+
version := "unknown"
37+
deployment, err := run.Clients.Kube.AppsV1().Deployments(ns).Get(ctx, "pipelines-as-code-controller", metav1.GetOptions{})
38+
if err != nil {
39+
continue
40+
}
41+
if val, ok := deployment.GetLabels()["app.kubernetes.io/version"]; ok {
42+
version = val
43+
}
44+
return ns, version, nil
45+
}
46+
return "", "", fmt.Errorf("cannot find your pipelines-as-code installation, check that it is installed and you have access")
47+
}
48+
49+
func install(ctx context.Context, run *params.Run, ios *cli.IOStreams, apiURL string) error {
50+
targetNs, version, err := getPacLocation(ctx, run)
51+
if err != nil {
52+
return err
53+
}
54+
var gapp *GithubApp
55+
jwtToken, err := app.GenerateJWT(ctx, targetNs, run)
56+
if err == nil {
57+
resp, err := app.GetReponse(ctx, "GET", fmt.Sprintf("%s/app", apiURL), jwtToken, run)
58+
if err != nil {
59+
return err
60+
}
61+
defer resp.Body.Close()
62+
data, err := io.ReadAll(resp.Body)
63+
if err != nil {
64+
return err
65+
}
66+
// parse json response
67+
if err := json.Unmarshal(data, &gapp); err != nil {
68+
return err
69+
}
70+
}
71+
repos, err := run.Clients.PipelineAsCode.PipelinesascodeV1alpha1().Repositories("").List(ctx, metav1.ListOptions{})
72+
if err != nil {
73+
return fmt.Errorf("cannot list alll repo on cluster, check your rights and that paac is installed: %w", err)
74+
}
75+
reposItems := &repos.Items
76+
args := struct {
77+
Gapp *GithubApp
78+
InstallNamespace string
79+
Version string
80+
Repos *[]v1alpha1.Repository
81+
CS *cli.ColorScheme
82+
}{
83+
Gapp: gapp,
84+
InstallNamespace: targetNs,
85+
Version: version,
86+
Repos: reposItems,
87+
CS: ios.ColorScheme(),
88+
}
89+
w := ansiterm.NewTabWriter(ios.Out, 0, 5, 3, ' ', tabwriter.TabIndent)
90+
t := template.Must(template.New("Describe Repository").Parse(infoTemplate))
91+
if err := t.Execute(w, args); err != nil {
92+
return err
93+
}
94+
95+
return w.Flush()
96+
}
97+
98+
func installCommand(run *params.Run, ioStreams *cli.IOStreams) *cobra.Command {
99+
var apiURL string
100+
cmd := &cobra.Command{
101+
Use: "install",
102+
Short: "Provides installation info for pipelines-as-code (admin only).",
103+
RunE: func(cmd *cobra.Command, args []string) error {
104+
ctx := context.Background()
105+
if err := run.Clients.NewClients(ctx, &run.Info); err != nil {
106+
return err
107+
}
108+
return install(ctx, run, ioStreams, apiURL)
109+
},
110+
Annotations: map[string]string{
111+
"commandType": "main",
112+
},
113+
}
114+
// add params for enteprise github
115+
cmd.PersistentFlags().StringVarP(&apiURL, "github-api-url", "", "https://api.github.com", "Github API URL")
116+
return cmd
117+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package info
2+
3+
import (
4+
_ "embed"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
9+
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
10+
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/clients"
11+
tcli "github.com/openshift-pipelines/pipelines-as-code/pkg/test/cli"
12+
testclient "github.com/openshift-pipelines/pipelines-as-code/pkg/test/clients"
13+
httptesting "github.com/openshift-pipelines/pipelines-as-code/pkg/test/http"
14+
"gotest.tools/v3/assert"
15+
"gotest.tools/v3/golden"
16+
appsv1 "k8s.io/api/apps/v1"
17+
corev1 "k8s.io/api/core/v1"
18+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19+
rtesting "knative.dev/pkg/reconciler/testing"
20+
)
21+
22+
// script kiddies, don't get too excited, this has been randomly generated with random words
23+
const fakePrivateKey = `-----BEGIN RSA PRIVATE KEY-----
24+
MIICXAIBAAKBgQC6GorZBeri0eVERMZQDFh5E1RMPjFk9AevaWr27yJse6eiUlos
25+
gY2L2vcZKLOrdvVR+TLLapIMFfg1E1qVr1iTHP3IiSCs1uW6NKDmxEQc9Uf/fG9c
26+
i56tGmTVxLkC94AvlVFmgxtWfHdP3lF2O0EcfRyIi6EIbGkWDqWQVEQG2wIDAQAB
27+
AoGAaKOd6FK0dB5Si6Uj4ERgxosAvfHGMh4n6BAc7YUd1ONeKR2myBl77eQLRaEm
28+
DMXRP+sfDVL5lUQRED62ky1JXlDc0TmdLiO+2YVyXI5Tbej0Q6wGVC25/HedguUX
29+
fw+MdKe8jsOOXVRLrJ2GfpKZ2CmOKGTm/hyrFa10TmeoTxkCQQDa4fvqZYD4vOwZ
30+
CplONnVk+PyQETj+mAyUiBnHEeLpztMImNLVwZbrmMHnBtCNx5We10oCLW+Qndfw
31+
Xi4LgliVAkEA2amSV+TZiUVQmm5j9yzon0rt1FK+cmVWfRS/JAUXyvl+Xh/J+7Gu
32+
QzoEGJNAnzkUIZuwhTfNRWlzURWYA8BVrwJAZFQhfJd6PomaTwAktU0REm9ulTrP
33+
vSNE4PBhoHX6ZOGAqfgi7AgIfYVPm+3rupE5a82TBtx8vvUa/fqtcGkW4QJAaL9t
34+
WPUeJyx/XMJxQzuOe1JA4CQt2LmiBLHeRoRY7ephgQSFXKYmed3KqNT8jWOXp5DY
35+
Q1QWaigUQdpFfNCrqwJBANLgWaJV722PhQXOCmR+INvZ7ksIhJVcq/x1l2BYOLw2
36+
QsncVExbMiPa9Oclo5qLuTosS8qwHm1MJEytp3/SkB8=
37+
-----END RSA PRIVATE KEY-----`
38+
39+
func TestInfo(t *testing.T) {
40+
ns1 := "ns1"
41+
ns2 := "ns2"
42+
somerepositories := []*v1alpha1.Repository{
43+
{
44+
ObjectMeta: metav1.ObjectMeta{
45+
Name: "repo1",
46+
Namespace: ns1,
47+
},
48+
Spec: v1alpha1.RepositorySpec{
49+
URL: "https://anurl.com",
50+
},
51+
},
52+
{
53+
ObjectMeta: metav1.ObjectMeta{
54+
Name: "repo2",
55+
Namespace: ns2,
56+
},
57+
Spec: v1alpha1.RepositorySpec{
58+
URL: "https://somewhere.com",
59+
},
60+
},
61+
}
62+
tests := []struct {
63+
name string
64+
wantErr bool
65+
secrets []*corev1.Secret
66+
repositories []*v1alpha1.Repository
67+
controllerLabels map[string]string
68+
controllerNs string
69+
}{
70+
{
71+
name: "with github app",
72+
repositories: somerepositories,
73+
controllerNs: "pipelines-as-code",
74+
secrets: []*corev1.Secret{
75+
{
76+
ObjectMeta: metav1.ObjectMeta{
77+
Name: "pipelines-as-code-secret",
78+
Namespace: "pipelines-as-code",
79+
},
80+
Data: map[string][]byte{
81+
"github-application-id": []byte("12345"),
82+
"github-private-key": []byte(fakePrivateKey),
83+
},
84+
},
85+
},
86+
},
87+
{
88+
name: "without github app",
89+
repositories: somerepositories,
90+
controllerNs: "pipelines-as-code",
91+
},
92+
{
93+
name: "no repos",
94+
controllerNs: "pipelines-as-code",
95+
},
96+
}
97+
for _, tt := range tests {
98+
t.Run(tt.name, func(t *testing.T) {
99+
namespaces := []*corev1.Namespace{
100+
{
101+
ObjectMeta: metav1.ObjectMeta{
102+
Name: ns1,
103+
},
104+
},
105+
{
106+
ObjectMeta: metav1.ObjectMeta{
107+
Name: ns2,
108+
},
109+
},
110+
}
111+
if tt.controllerLabels == nil {
112+
tt.controllerLabels = map[string]string{
113+
"app.kubernetes.io/version": "testing",
114+
}
115+
}
116+
117+
tdata := testclient.Data{
118+
Namespaces: namespaces,
119+
Repositories: tt.repositories,
120+
Deployments: []*appsv1.Deployment{
121+
{
122+
ObjectMeta: metav1.ObjectMeta{
123+
Name: "pipelines-as-code-controller",
124+
Labels: tt.controllerLabels,
125+
Namespace: tt.controllerNs,
126+
},
127+
},
128+
},
129+
Secret: tt.secrets,
130+
}
131+
apiURL := "http://github.url"
132+
ghAppJSON := `{
133+
"installations_count": 5,
134+
"description": "my beautiful app",
135+
"html_url": "http://github.url/app/myapp",
136+
"external_url": "http://myapp.url",
137+
"created_at": "2023-03-22T12:29:10Z",
138+
"name": "myapp"
139+
}`
140+
httpTestClient := httptesting.MakeHTTPTestClient(map[string]map[string]string{
141+
apiURL + "/app": {
142+
"body": ghAppJSON,
143+
"code": "200",
144+
},
145+
})
146+
147+
ctx, _ := rtesting.SetupFakeContext(t)
148+
stdata, _ := testclient.SeedTestData(t, ctx, tdata)
149+
cs := &params.Run{
150+
Clients: clients.Clients{
151+
PipelineAsCode: stdata.PipelineAsCode,
152+
Kube: stdata.Kube,
153+
HTTP: *httpTestClient,
154+
},
155+
}
156+
157+
io, out := tcli.NewIOStream()
158+
err := install(ctx, cs, io, apiURL)
159+
assert.NilError(t, err)
160+
golden.Assert(t, out.String(), fmt.Sprintf("%s.golden", t.Name()))
161+
})
162+
}
163+
}

pkg/cmd/tknpac/info/root.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package info
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
"github.com/openshift-pipelines/pipelines-as-code/pkg/cli"
7+
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
8+
)
9+
10+
func Root(clients *params.Run, ioStreams *cli.IOStreams) *cobra.Command {
11+
cmd := &cobra.Command{
12+
Use: "info",
13+
Aliases: []string{},
14+
Short: "Add Information",
15+
Long: `Information about your Pipelines as Code installation`,
16+
SilenceUsage: true,
17+
Annotations: map[string]string{
18+
"commandType": "main",
19+
},
20+
}
21+
22+
cmd.AddCommand(installCommand(clients, ioStreams))
23+
return cmd
24+
}

0 commit comments

Comments
 (0)