Skip to content

Commit c969c09

Browse files
authored
fix(plugins): add missing scope-config and missing projects in grafana for argocd (#8642)
The scope-config was missing in the initial implementation, which prevent the DORA metrics to be collected. In Grafana, you could not select the projects, so only ALL was available as an option. I also moved the graphs under the correct sections. For the comment in the issue #5207 (comment)
1 parent 96f6253 commit c969c09

7 files changed

Lines changed: 223 additions & 12 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package tasks
19+
20+
import (
21+
"fmt"
22+
"reflect"
23+
"strings"
24+
25+
"github.com/apache/incubator-devlake/core/dal"
26+
"github.com/apache/incubator-devlake/core/errors"
27+
"github.com/apache/incubator-devlake/core/models/domainlayer"
28+
"github.com/apache/incubator-devlake/core/models/domainlayer/devops"
29+
"github.com/apache/incubator-devlake/core/models/domainlayer/didgen"
30+
"github.com/apache/incubator-devlake/core/plugin"
31+
"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
32+
"github.com/apache/incubator-devlake/plugins/argocd/models"
33+
)
34+
35+
var ConvertApplicationsMeta = plugin.SubTaskMeta{
36+
Name: "convertApplications",
37+
EntryPoint: ConvertApplications,
38+
EnabledByDefault: true,
39+
Description: "Convert ArgoCD applications into CICD scopes",
40+
DomainTypes: []string{plugin.DOMAIN_TYPE_CICD},
41+
DependencyTables: []string{models.ArgocdApplication{}.TableName()},
42+
ProductTables: []string{devops.CicdScope{}.TableName()},
43+
}
44+
45+
func ConvertApplications(taskCtx plugin.SubTaskContext) errors.Error {
46+
data := taskCtx.GetData().(*ArgocdTaskData)
47+
db := taskCtx.GetDal()
48+
49+
cursor, err := db.Cursor(
50+
dal.From(&models.ArgocdApplication{}),
51+
dal.Where("connection_id = ?", data.Options.ConnectionId),
52+
)
53+
if err != nil {
54+
return err
55+
}
56+
defer cursor.Close()
57+
58+
scopeIdGen := didgen.NewDomainIdGenerator(&models.ArgocdApplication{})
59+
60+
converter, err := api.NewDataConverter(api.DataConverterArgs{
61+
InputRowType: reflect.TypeOf(models.ArgocdApplication{}),
62+
Input: cursor,
63+
RawDataSubTaskArgs: api.RawDataSubTaskArgs{
64+
Ctx: taskCtx,
65+
Table: RAW_APPLICATION_TABLE,
66+
Params: models.ArgocdApiParams{
67+
ConnectionId: data.Options.ConnectionId,
68+
Name: data.Options.ApplicationName,
69+
},
70+
},
71+
Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
72+
application := inputRow.(*models.ArgocdApplication)
73+
scopeId := scopeIdGen.Generate(application.ConnectionId, application.Name)
74+
scope := buildCicdScopeFromApplication(application, scopeId)
75+
return []interface{}{scope}, nil
76+
},
77+
})
78+
if err != nil {
79+
return err
80+
}
81+
82+
return converter.Execute()
83+
}
84+
85+
func buildCicdScopeFromApplication(app *models.ArgocdApplication, scopeId string) *devops.CicdScope {
86+
scope := &devops.CicdScope{
87+
DomainEntity: domainlayer.NewDomainEntity(scopeId),
88+
Name: app.Name,
89+
Description: describeApplicationScope(app),
90+
Url: firstNonEmpty(app.RepoURL, app.DestServer),
91+
CreatedDate: app.CreatedDate,
92+
}
93+
return scope
94+
}
95+
96+
func describeApplicationScope(app *models.ArgocdApplication) string {
97+
parts := make([]string, 0, 3)
98+
if app.Project != "" {
99+
parts = append(parts, fmt.Sprintf("Project: %s", app.Project))
100+
}
101+
if app.Namespace != "" {
102+
parts = append(parts, fmt.Sprintf("Namespace: %s", app.Namespace))
103+
}
104+
if app.DestNamespace != "" || app.DestServer != "" {
105+
dest := strings.TrimSpace(fmt.Sprintf("%s/%s", app.DestServer, app.DestNamespace))
106+
dest = strings.Trim(dest, "/")
107+
if dest != "" {
108+
parts = append(parts, fmt.Sprintf("Destination: %s", dest))
109+
}
110+
}
111+
return strings.Join(parts, " | ")
112+
}
113+
114+
func firstNonEmpty(values ...string) string {
115+
for _, v := range values {
116+
if strings.TrimSpace(v) != "" {
117+
return v
118+
}
119+
}
120+
return ""
121+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package tasks
19+
20+
import (
21+
"testing"
22+
"time"
23+
24+
"github.com/apache/incubator-devlake/plugins/argocd/models"
25+
"github.com/stretchr/testify/assert"
26+
)
27+
28+
func TestDescribeApplicationScopeBuildsSummary(t *testing.T) {
29+
desc := describeApplicationScope(&models.ArgocdApplication{
30+
Project: "observability",
31+
Namespace: "argocd",
32+
DestServer: "https://k8s.example.com",
33+
DestNamespace: "prod",
34+
})
35+
assert.Equal(t, "Project: observability | Namespace: argocd | Destination: https://k8s.example.com/prod", desc)
36+
}
37+
38+
func TestBuildCicdScopeFromApplication(t *testing.T) {
39+
created := time.Now()
40+
scope := buildCicdScopeFromApplication(&models.ArgocdApplication{
41+
Name: "test-app",
42+
RepoURL: "https://git.example.com/app.git",
43+
CreatedDate: &created,
44+
}, "argocd:app:1")
45+
46+
assert.Equal(t, "argocd:app:1", scope.Id)
47+
assert.Equal(t, "test-app", scope.Name)
48+
assert.Equal(t, "https://git.example.com/app.git", scope.Url)
49+
if assert.NotNil(t, scope.CreatedDate) {
50+
assert.Equal(t, created.UTC(), scope.CreatedDate.UTC())
51+
}
52+
}

backend/plugins/argocd/tasks/register.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func CollectDataTaskMetas() []plugin.SubTaskMeta {
3030
return []plugin.SubTaskMeta{
3131
CollectApplicationsMeta,
3232
ExtractApplicationsMeta,
33+
ConvertApplicationsMeta,
3334
CollectSyncOperationsMeta,
3435
ExtractSyncOperationsMeta,
3536
ConvertSyncOperationsMeta,

config-ui/src/plugins/components/scope-config-form/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { AzureTransformation } from '@/plugins/register/azure';
3434
import { TapdTransformation } from '@/plugins/register/tapd';
3535
import { BambooTransformation } from '@/plugins/register/bamboo';
3636
import { CircleCITransformation } from '@/plugins/register/circleci';
37+
import { ArgoCDTransformation } from '@/plugins/register/argocd';
3738
import { DOC_URL } from '@/release';
3839
import { operator } from '@/utils';
3940

@@ -86,7 +87,7 @@ export const ScopeConfigForm = ({
8687
setName(forceCreate ? `${res.name}-copy` : res.name);
8788
setEntities(res.entities ?? []);
8889
setTransformation(omit(res, ['id', 'connectionId', 'name', 'entities', 'createdAt', 'updatedAt']));
89-
} catch {}
90+
} catch { }
9091
})();
9192
}, [scopeConfigId]);
9293

@@ -193,6 +194,14 @@ export const ScopeConfigForm = ({
193194
)}
194195

195196
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 16 }}>
197+
{plugin === 'argocd' && (
198+
<ArgoCDTransformation
199+
entities={entities}
200+
transformation={transformation}
201+
setTransformation={setTransformation}
202+
/>
203+
)}
204+
196205
{plugin === 'azuredevops' && (
197206
<AzureTransformation
198207
entities={entities}

config-ui/src/plugins/register/argocd/config.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const ArgoCDConfig: IPluginConfig = {
2929
sort: 1,
3030
isBeta: true,
3131
connection: {
32-
docLink: '',
32+
docLink: DOC_URL.PLUGIN.ARGOCD.BASIS,
3333
initialValues: {
3434
endpoint: 'https://',
3535
},

config-ui/src/release/stable.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ const URLS = {
2323
},
2424
DORA: 'https://devlake.apache.org/docs/DORA/',
2525
PLUGIN: {
26+
ARGOCD: {
27+
BASIS: 'https://devlake.apache.org/docs/Configuration/ArgoCD',
28+
TRANSFORMATION: 'https://devlake.apache.org/docs/Configuration/ArgoCD#step-3---adding-transformation-rules-optional',
29+
},
2630
AZUREDEVOPS: {
2731
BASIS: 'https://devlake.apache.org/docs/Configuration/AzureDevOps',
2832
RATE_LIMIT: 'https://devlake.apache.org/docs/Configuration/AzureDevOps/#custom-rate-limit-optional',

grafana/dashboards/ArgoCD.json

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,12 @@
302302
},
303303
{
304304
"collapsed": false,
305+
"gridPos": {
306+
"h": 1,
307+
"w": 24,
308+
"x": 0,
309+
"y": 9
310+
},
305311
"id": 6,
306312
"panels": [],
307313
"title": "2. Deployment Trends",
@@ -328,7 +334,7 @@
328334
"h": 8,
329335
"w": 8,
330336
"x": 0,
331-
"y": 9
337+
"y": 10
332338
},
333339
"id": 7,
334340
"options": {
@@ -394,7 +400,7 @@
394400
"h": 8,
395401
"w": 8,
396402
"x": 8,
397-
"y": 9
403+
"y": 10
398404
},
399405
"id": 8,
400406
"options": {
@@ -460,7 +466,7 @@
460466
"h": 8,
461467
"w": 8,
462468
"x": 16,
463-
"y": 9
469+
"y": 10
464470
},
465471
"id": 15,
466472
"options": {
@@ -505,6 +511,12 @@
505511
},
506512
{
507513
"collapsed": false,
514+
"gridPos": {
515+
"h": 1,
516+
"w": 24,
517+
"x": 0,
518+
"y": 18
519+
},
508520
"id": 9,
509521
"panels": [],
510522
"title": "3. Deployment Durations",
@@ -533,7 +545,7 @@
533545
"h": 8,
534546
"w": 6,
535547
"x": 0,
536-
"y": 17
548+
"y": 19
537549
},
538550
"id": 10,
539551
"options": {
@@ -588,7 +600,7 @@
588600
"h": 8,
589601
"w": 9,
590602
"x": 6,
591-
"y": 17
603+
"y": 19
592604
},
593605
"id": 11,
594606
"options": {
@@ -662,7 +674,7 @@
662674
"h": 8,
663675
"w": 9,
664676
"x": 15,
665-
"y": 17
677+
"y": 19
666678
},
667679
"id": 12,
668680
"options": {
@@ -722,7 +734,7 @@
722734
"h": 7,
723735
"w": 24,
724736
"x": 0,
725-
"y": 25
737+
"y": 27
726738
},
727739
"id": 16,
728740
"options": {
@@ -755,6 +767,12 @@
755767
},
756768
{
757769
"collapsed": false,
770+
"gridPos": {
771+
"h": 1,
772+
"w": 24,
773+
"x": 0,
774+
"y": 34
775+
},
758776
"id": 13,
759777
"panels": [],
760778
"title": "4. Deployment Details",
@@ -791,7 +809,7 @@
791809
"h": 9,
792810
"w": 24,
793811
"x": 0,
794-
"y": 32
812+
"y": 35
795813
},
796814
"id": 14,
797815
"options": {
@@ -830,6 +848,12 @@
830848
},
831849
{
832850
"collapsed": false,
851+
"gridPos": {
852+
"h": 1,
853+
"w": 24,
854+
"x": 0,
855+
"y": 44
856+
},
833857
"id": 17,
834858
"panels": [],
835859
"title": "5. Images",
@@ -856,7 +880,7 @@
856880
"h": 8,
857881
"w": 12,
858882
"x": 0,
859-
"y": 41
883+
"y": 45
860884
},
861885
"id": 18,
862886
"options": {
@@ -915,7 +939,7 @@
915939
"h": 8,
916940
"w": 12,
917941
"x": 12,
918-
"y": 41
942+
"y": 45
919943
},
920944
"id": 19,
921945
"options": {

0 commit comments

Comments
 (0)