Skip to content

Commit 1feb2af

Browse files
authored
simplify (#8)
1 parent 0a7e274 commit 1feb2af

File tree

5 files changed

+131
-181
lines changed

5 files changed

+131
-181
lines changed

README.md

Lines changed: 45 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,33 @@ spec:
1515
entrypoint: main
1616
templates:
1717
- name: main
18-
plugin:
19-
argocd:
20-
actions:
21-
- - app:
22-
sync:
23-
apps:
18+
steps:
19+
- - name: sync
20+
template: sync
21+
arguments:
22+
parameters:
23+
- name: apps
24+
value: |
2425
- name: guestbook-frontend
2526
- name: guestbook-backend
27+
- - name: diff
28+
template: diff
29+
- name: sync
30+
inputs:
31+
parameters:
32+
- name: apps
33+
plugin:
34+
argocd:
35+
app:
36+
sync:
37+
apps: "{{inputs.parameters.apps}}"
38+
- name: diff
39+
plugin:
40+
argocd:
41+
app:
42+
diff:
43+
app:
44+
name: guestbook-frontend
2645
```
2746
2847
## Getting Started
@@ -71,30 +90,6 @@ The `actions` field of the plugin config accepts a nested list of actions. Paren
7190
child lists are executed in parallel. This allows you to run multiple actions in parallel, and multiple groups of
7291
actions in sequence.
7392

74-
### Running syncs in sequence
75-
76-
```yaml
77-
apiVersion: argoproj.io/v1alpha1
78-
kind: Workflow
79-
metadata:
80-
generateName: argocd-sequence-example-
81-
spec:
82-
entrypoint: main
83-
templates:
84-
- name: main
85-
plugin:
86-
argocd:
87-
actions:
88-
- - app:
89-
sync:
90-
apps:
91-
- name: guestbook-backend
92-
- - app:
93-
sync:
94-
apps:
95-
- name: guestbook-frontend
96-
```
97-
9893
### Setting sync options
9994

10095
```yaml
@@ -108,14 +103,13 @@ spec:
108103
- name: main
109104
plugin:
110105
argocd:
111-
actions:
112-
- - app:
113-
sync:
114-
apps:
115-
- name: guestbook-backend
116-
options:
117-
- ServerSideApply=true
118-
- Validate=true
106+
app:
107+
sync:
108+
apps: |
109+
- name: guestbook-backend
110+
options: |
111+
- ServerSideApply=true
112+
- Validate=true
119113
```
120114

121115
### Setting a timeout
@@ -133,12 +127,14 @@ spec:
133127
- name: main
134128
plugin:
135129
argocd:
136-
actions:
137-
- - app:
138-
sync:
139-
apps:
140-
- name: guestbook-backend
141-
timeout: 30s
130+
app:
131+
sync:
132+
apps: |
133+
- name: guestbook-backend
134+
options: |
135+
- ServerSideApply=true
136+
- Validate=true
137+
timeout: 30s
142138
```
143139

144140
### Specifying the Application's namespace
@@ -157,12 +153,11 @@ spec:
157153
- name: main
158154
plugin:
159155
argocd:
160-
actions:
161-
- - app:
162-
sync:
163-
apps:
164-
- name: guestbook-backend
165-
namespace: my-apps-namespace
156+
app:
157+
sync:
158+
apps: |
159+
- name: guestbook-backend
160+
namespace: my-apps-namespace
166161
```
167162

168163
## Contributing

examples/argocd-example-wf.yaml

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
apiVersion: argoproj.io/v1alpha1
22
kind: Workflow
33
metadata:
4-
generateName: argocd-example-
4+
name: test
55
spec:
66
ttlStrategy:
77
secondsAfterCompletion: 300
@@ -11,19 +11,31 @@ spec:
1111
entrypoint: main
1212
templates:
1313
- name: main
14+
steps:
15+
- - name: sync
16+
template: sync
17+
arguments:
18+
parameters:
19+
- name: apps
20+
value: |
21+
- name: guestbook
22+
- - name: diff
23+
template: diff
24+
- name: sync
25+
inputs:
26+
parameters:
27+
- name: apps
1428
plugin:
1529
argocd:
16-
# `actions` is a nested list. Items of inner arrays run in parallel. Top-level arrays run in sequence.
17-
# In this example, each inner array has only one item. The syncs run in sequence.
18-
actions:
19-
# TODO: support other action types, e.g. `cluster` and `repository`.
20-
- - app:
21-
diff:
22-
app:
23-
name: guestbook
24-
revision: 382b85852fa33f13d4987424853c5206b9231ff0
25-
# Uncomment this to test a failed sync.
26-
# - - app:
27-
# sync:
28-
# apps:
29-
# - name: not-real
30+
app:
31+
sync:
32+
apps: "{{inputs.parameters.apps}}"
33+
- name: diff
34+
plugin:
35+
argocd:
36+
# TODO: support other action types, e.g. `cluster` and `repository`.
37+
app:
38+
diff:
39+
app:
40+
name: guestbook
41+
revision: 382b85852fa33f13d4987424853c5206b9231ff0

internal/argocd_executor.go

Lines changed: 39 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"errors"
77
"fmt"
88
"log"
9-
"sort"
109
"strings"
1110
"sync"
1211
"time"
@@ -19,6 +18,7 @@ import (
1918
wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1"
2019
"github.com/argoproj/argo-workflows/v3/pkg/plugins/executor"
2120
"github.com/argoproj/gitops-engine/pkg/sync/hook"
21+
"gopkg.in/yaml.v3"
2222
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2323
"k8s.io/utils/pointer"
2424

@@ -49,149 +49,89 @@ func (e *ApiExecutor) Execute(args executor.ExecuteTemplateArgs) executor.Execut
4949
return errorResponse(err)
5050
}
5151

52-
totalActionGroups := len(plugin.ArgoCD.Actions)
53-
actionGroupCount := 0
54-
55-
var groupOutputs [][]any
56-
for i, actionGroup := range plugin.ArgoCD.Actions {
57-
outputs, err := e.runActionsParallel(actionGroup)
58-
if err != nil {
59-
return failedResponse(wfv1.Progress(fmt.Sprintf("%d/%d", actionGroupCount, totalActionGroups)), fmt.Errorf("action group %d of %d failed: %w", i+1, totalActionGroups, err))
60-
}
61-
groupOutputs = append(groupOutputs, outputs)
62-
actionGroupCount += 1
63-
}
64-
65-
outputsJson, err := json.Marshal(groupOutputs)
52+
output, err := e.runAction(plugin.ArgoCD)
6653
if err != nil {
67-
err = fmt.Errorf("failed to marshal outputs to JSON: %w", err)
68-
log.Println(err.Error())
69-
return errorResponse(err)
54+
return failedResponse(wfv1.Progress(fmt.Sprintf("0/1")), fmt.Errorf("action failed: %w", err))
7055
}
7156

72-
outputsJsonAnyString := wfv1.AnyString(outputsJson)
73-
7457
return executor.ExecuteTemplateReply{
7558
Node: &wfv1.NodeResult{
7659
Phase: wfv1.NodeSucceeded,
77-
Message: "Actions completed",
78-
Progress: wfv1.Progress(fmt.Sprintf("%d/%d", actionGroupCount, totalActionGroups)),
60+
Message: "Action completed",
61+
Progress: "1/1",
7962
Outputs: &wfv1.Outputs{
80-
Parameters: []wfv1.Parameter{
81-
{
82-
Name: "outputs",
83-
Value: &outputsJsonAnyString,
84-
},
85-
},
63+
Result: pointer.String(output),
8664
},
8765
},
8866
}
8967
}
9068

91-
type actionResult struct {
92-
index int
93-
err error
94-
output any
95-
}
96-
97-
// runActionsParallel runs the given group of actions in parallel and returns aggregated errors, if any.
98-
func (e *ApiExecutor) runActionsParallel(actionGroup []ActionSpec) ([]any, error) {
69+
// runAction runs the given action and returns outputs or errors, if any.
70+
func (e *ApiExecutor) runAction(action ActionSpec) (out string, err error) {
9971
closer, appClient, err := e.apiClient.NewApplicationClient()
10072
if err != nil {
101-
return nil, fmt.Errorf("failed to initialize Application API client: %w", err)
73+
return "", fmt.Errorf("failed to initialize Application API client: %w", err)
10274
}
10375
defer io.Close(closer)
10476

10577
closer, settingsClient, err := e.apiClient.NewSettingsClient()
10678
if err != nil {
107-
return nil, fmt.Errorf("failed to initialize Application API client: %w", err)
79+
return "", fmt.Errorf("failed to initialize Application API client: %w", err)
10880
}
10981
defer io.Close(closer)
11082

111-
wg := sync.WaitGroup{}
112-
actionResults := make(chan actionResult, len(actionGroup))
113-
for i, action := range actionGroup {
114-
i := i
115-
action := action
116-
if action.App == nil {
117-
return nil, fmt.Errorf("action %d of %d is missing a valid action type (sync or diff)", i+1, len(actionGroup))
118-
}
119-
if action.App.Sync != nil && action.App.Diff != nil {
120-
return nil, fmt.Errorf("action %d of %d has both multiple types of actions defined", i+1, len(actionGroup))
121-
}
122-
if action.App.Sync == nil && action.App.Diff == nil {
123-
return nil, fmt.Errorf("action %d of %d has no action defined", i+1, len(actionGroup))
124-
}
125-
wg.Add(1)
126-
go func(actionNum int) {
127-
defer wg.Done()
128-
if action.App.Sync != nil {
129-
err := syncAppsParallel(*action.App.Sync, action.Timeout, appClient)
130-
if err != nil {
131-
actionResults <- actionResult{index: i, err: fmt.Errorf("parallel item %d of %d failed: failed to sync Application(s): %w", actionNum+1, len(actionGroup), err)}
132-
} else {
133-
actionResults <- actionResult{index: i, output: ""}
134-
}
135-
}
136-
if action.App.Diff != nil {
137-
diff, err := diffApp(*action.App.Diff, action.Timeout, appClient, settingsClient)
138-
if err != nil {
139-
actionResults <- actionResult{index: i, err: fmt.Errorf("parallel item %d of %d failed: failed to diff Application: %w", actionNum+1, len(actionGroup), err)}
140-
} else {
141-
actionResults <- actionResult{index: i, output: diff}
142-
}
143-
}
144-
}(i)
83+
if action.App == nil {
84+
return "", errors.New("action is missing a valid action type (i.e. an 'app' block)")
14585
}
146-
go func() {
147-
wg.Wait()
148-
close(actionResults)
149-
}()
150-
var results []actionResult
151-
for out := range actionResults {
152-
results = append(results, out)
86+
if action.App.Sync != nil && action.App.Diff != nil {
87+
return "", errors.New("action has multiple types of action defined (both sync and diff)")
15388
}
154-
sort.Slice(results, func(i, j int) bool {
155-
return results[i].index < results[j].index
156-
})
157-
hasError := false
158-
var errorMessages []string
159-
var outputs []any
160-
for _, result := range results {
161-
if result.err != nil {
162-
hasError = true
163-
errorMessages = append(errorMessages, result.err.Error())
164-
outputs = append(outputs, nil)
165-
} else {
166-
errorMessages = append(errorMessages, "")
167-
outputs = append(outputs, result.output)
89+
if action.App.Sync == nil && action.App.Diff == nil {
90+
return "", errors.New("app action has no action type specified (must be sync or diff)")
91+
}
92+
93+
if action.App.Sync != nil {
94+
err = syncAppsParallel(*action.App.Sync, action.Timeout, appClient)
95+
if err != nil {
16896
}
16997
}
170-
if hasError {
171-
return nil, fmt.Errorf("one or more actions failed: %s", strings.Join(errorMessages, "; "))
98+
if action.App.Diff != nil {
99+
out, err = diffApp(*action.App.Diff, action.Timeout, appClient, settingsClient)
100+
if err != nil {
101+
}
172102
}
173-
return outputs, nil
103+
return out, err
174104
}
175105

176106
// syncAppsParallel loops over the apps in a SyncAction and syncs them in parallel. It waits for all responses and then
177107
// aggregates any errors.
178108
func syncAppsParallel(action SyncAction, timeout string, appClient application.ApplicationServiceClient) error {
109+
var apps []App
110+
err := yaml.Unmarshal([]byte(action.Apps), &apps)
111+
if err != nil {
112+
return fmt.Errorf("failed to unmarshal apps: %w", err)
113+
}
114+
var options []string
115+
err = yaml.Unmarshal([]byte(action.Options), &options)
116+
if err != nil {
117+
return fmt.Errorf("failed to unmarshal options: %w", err)
118+
}
179119
ctx, cancel, err := durationStringToContext(timeout)
180120
if err != nil {
181121
return fmt.Errorf("failed get action context: %w", err)
182122
}
183123
defer cancel()
184124
wg := sync.WaitGroup{}
185125
errChan := make(chan error, len(action.Apps))
186-
for _, app := range action.Apps {
126+
for _, app := range apps {
187127
app := app
188128
wg.Add(1)
189129
go func() {
190130
defer wg.Done()
191131
_, err := appClient.Sync(ctx, &application.ApplicationSyncRequest{
192132
Name: pointer.String(app.Name),
193133
AppNamespace: pointer.String(app.Namespace),
194-
SyncOptions: &application.SyncOptions{Items: action.Options},
134+
SyncOptions: &application.SyncOptions{Items: options},
195135
})
196136
if err != nil {
197137
errChan <- fmt.Errorf("failed to sync app %q: %w", app.Name, err)
@@ -308,7 +248,7 @@ func diffApp(action DiffAction, timeout string, appClient application.Applicatio
308248
target = item.target
309249
}
310250

311-
diff, err = GetDiff(action.App.Name, live, target)
251+
diff, err = GetDiff(live, target)
312252
if err != nil {
313253
return "", fmt.Errorf("failed to get diff: %w", err)
314254
}

0 commit comments

Comments
 (0)