1
1
package httpapi
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
6
+ "crypto/tls"
5
7
"encoding/json"
6
8
"fmt"
9
+ "io/ioutil"
7
10
"log"
8
11
"net/http"
9
12
"net/url"
10
13
"strings"
11
14
12
15
argoV1aplha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
13
16
"github.com/julienschmidt/httprouter"
17
+ corev1 "k8s.io/api/core/v1"
14
18
"k8s.io/apimachinery/pkg/types"
15
19
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
16
20
"sigs.k8s.io/yaml"
@@ -26,7 +30,13 @@ var DefaultSecretRef = types.NamespacedName{
26
30
Namespace : "pipelines-app-delivery" ,
27
31
}
28
32
29
- const defaultRef = "HEAD"
33
+ const (
34
+ defaultRef = "HEAD"
35
+ defaultArgoCDInstance = "openshift-gitops"
36
+ defaultArgocdNamespace = "openshift-gitops"
37
+ )
38
+
39
+ var baseURL = fmt .Sprintf ("https://%s-server.%s.svc.cluster.local" , defaultArgoCDInstance , defaultArgocdNamespace )
30
40
31
41
// APIRouter is an HTTP API for accessing app configurations.
32
42
type APIRouter struct {
@@ -51,9 +61,16 @@ func NewRouter(c git.ClientFactory, s secrets.SecretGetter, kc ctrlclient.Client
51
61
api .HandlerFunc (http .MethodGet , "/pipelines" , api .GetPipelines )
52
62
api .HandlerFunc (http .MethodGet , "/applications" , api .ListApplications )
53
63
api .HandlerFunc (http .MethodGet , "/environments/:env/application/:app" , api .GetApplication )
64
+ api .HandlerFunc (http .MethodGet , "/environment/:env/application/:app" , api .GetApplicationDetails )
54
65
return api
55
66
}
56
67
68
+ type RevisionMeta struct {
69
+ Author string `json:"author"`
70
+ Message string `json:"message"`
71
+ Revision string `json:"revision"`
72
+ }
73
+
57
74
// GetPipelines fetches and returns the pipeline body.
58
75
func (a * APIRouter ) GetPipelines (w http.ResponseWriter , r * http.Request ) {
59
76
urlToFetch := r .URL .Query ().Get ("url" )
@@ -201,6 +218,86 @@ func (a *APIRouter) ListApplications(w http.ResponseWriter, r *http.Request) {
201
218
marshalResponse (w , applicationsToAppsResponse (apps , parsedRepoURL .String ()))
202
219
}
203
220
221
+ func (a * APIRouter ) GetApplicationDetails (w http.ResponseWriter , r * http.Request ) {
222
+ params := httprouter .ParamsFromContext (r .Context ())
223
+ envName , appName := params .ByName ("env" ), params .ByName ("app" )
224
+ app := & argoV1aplha1.Application {}
225
+ var lastDeployed , revision string
226
+
227
+ repoURL := strings .TrimSpace (r .URL .Query ().Get ("url" ))
228
+ if repoURL == "" {
229
+ log .Println ("ERROR: please provide a valid GitOps repo URL" )
230
+ http .Error (w , "please provide a valid GitOps repo URL" , http .StatusBadRequest )
231
+ return
232
+ }
233
+
234
+ parsedRepoURL , err := url .Parse (repoURL )
235
+ if err != nil {
236
+ log .Printf ("ERROR: failed to parse URL, error: %v" , err )
237
+ http .Error (w , fmt .Sprintf ("failed to parse URL, error: %v" , err ), http .StatusBadRequest )
238
+ return
239
+ }
240
+
241
+ parsedRepoURL .RawQuery = ""
242
+
243
+ appList := & argoV1aplha1.ApplicationList {}
244
+ var listOptions []ctrlclient.ListOption
245
+
246
+ listOptions = append (listOptions , ctrlclient .InNamespace ("" ), ctrlclient.MatchingFields {
247
+ "metadata.name" : fmt .Sprintf ("%s-%s" , envName , appName ),
248
+ })
249
+
250
+ err = a .k8sClient .List (r .Context (), appList , listOptions ... )
251
+ if err != nil {
252
+ log .Printf ("ERROR: failed to get application list: %v" , err )
253
+ http .Error (w , fmt .Sprintf ("failed to get list of application, err: %v" , err ), http .StatusBadRequest )
254
+ return
255
+ }
256
+
257
+ for _ , a := range appList .Items {
258
+ if a .Spec .Source .RepoURL == parsedRepoURL .String () {
259
+ app = & a
260
+ }
261
+ }
262
+
263
+ if app == nil {
264
+ log .Printf ("ERROR: failed to get application %s: %v" , appName , err )
265
+ http .Error (w , fmt .Sprintf ("failed to get the application %s, err: %v" , appName , err ), http .StatusBadRequest )
266
+ return
267
+ }
268
+
269
+ if len (app .Status .History ) > 0 {
270
+ revision = app .Status .History [len (app .Status .History )- 1 ].Revision
271
+ t := app .Status .History [len (app .Status .History )- 1 ].DeployedAt
272
+ if ! t .IsZero () {
273
+ lastDeployed = t .String ()
274
+ }
275
+ }
276
+
277
+ commitInfo , err := a .getCommitInfo (app .Name , revision )
278
+ if err != nil {
279
+ log .Printf ("ERROR: failed to retrieve revision metadata for app %s: %v" , appName , err )
280
+ http .Error (w , fmt .Sprintf ("failed to retrieve revision metadata for app %s, err: %v" , appName , err ), http .StatusBadRequest )
281
+ return
282
+ }
283
+
284
+ revisionMeta := RevisionMeta {
285
+ Author : strings .Split (commitInfo ["author" ], " " )[0 ],
286
+ Message : commitInfo ["message" ],
287
+ Revision : revision ,
288
+ }
289
+
290
+ appEnv := map [string ]interface {}{
291
+ "environment" : app .Spec .Destination .Namespace ,
292
+ "cluster" : app .Spec .Destination .Server ,
293
+ "lastDeployed" : lastDeployed ,
294
+ "status" : app .Status .Sync .Status ,
295
+ "revision" : revisionMeta ,
296
+ }
297
+
298
+ marshalResponse (w , appEnv )
299
+ }
300
+
204
301
func (a * APIRouter ) getAuthToken (ctx context.Context , req * http.Request ) (string , error ) {
205
302
token := AuthToken (ctx )
206
303
secret , ok := secretRefFromQuery (req .URL .Query ())
@@ -253,3 +350,72 @@ func marshalResponse(w http.ResponseWriter, v interface{}) {
253
350
log .Printf ("failed to encode response: %s" , err )
254
351
}
255
352
}
353
+
354
+ func (a * APIRouter ) getCommitInfo (app , revision string ) (map [string ]string , error ) {
355
+ client := & http.Client {Transport : & http.Transport {TLSClientConfig : & tls.Config {InsecureSkipVerify : true }}}
356
+ argocdCreds := & corev1.Secret {}
357
+ err := a .k8sClient .Get (context .TODO (),
358
+ types.NamespacedName {
359
+ Name : defaultArgoCDInstance + "-cluster" ,
360
+ Namespace : defaultArgocdNamespace ,
361
+ }, argocdCreds )
362
+ if err != nil {
363
+ return nil , err
364
+ }
365
+
366
+ payload := map [string ]string {
367
+ "username" : "admin" ,
368
+ "password" : string (argocdCreds .Data ["admin.password" ]),
369
+ }
370
+ payloadBytes , err := json .Marshal (payload )
371
+ if err != nil {
372
+ return nil , err
373
+ }
374
+
375
+ resp , err := client .Post (fmt .Sprintf ("%s/api/v1/session" , baseURL ),
376
+ "application/json" , bytes .NewBuffer (payloadBytes ))
377
+ if err != nil {
378
+ return nil , err
379
+ }
380
+ defer resp .Body .Close ()
381
+ bodyBytes , err := ioutil .ReadAll (resp .Body )
382
+ if err != nil {
383
+ return nil , err
384
+ }
385
+
386
+ m := make (map [string ]string )
387
+ err = json .Unmarshal (bodyBytes , & m )
388
+ if err != nil {
389
+ return nil , err
390
+ }
391
+
392
+ if token , ok := m ["token" ]; ! ok || token == "" {
393
+ return nil , fmt .Errorf ("failed to retrieve JWT from the api-server" )
394
+ }
395
+
396
+ u := fmt .Sprintf ("%s/api/v1/applications/%s/revisions/%s/metadata" , baseURL , app , revision )
397
+ req , err := http .NewRequest ("GET" , u , nil )
398
+ if err != nil {
399
+ return nil , err
400
+ }
401
+
402
+ req .Header .Add ("Authorization" , fmt .Sprintf ("Bearer %s" , m ["token" ]))
403
+ req .Header .Add ("content-type" , "application/json" )
404
+
405
+ resp , err = client .Do (req )
406
+ if err != nil {
407
+ return nil , err
408
+ }
409
+
410
+ defer resp .Body .Close ()
411
+ bodyBytes , err = ioutil .ReadAll (resp .Body )
412
+ if err != nil {
413
+ return nil , err
414
+ }
415
+
416
+ err = json .Unmarshal (bodyBytes , & m )
417
+ if err != nil {
418
+ return nil , err
419
+ }
420
+ return m , nil
421
+ }
0 commit comments