Skip to content

Commit 71bffe3

Browse files
feat: add support for appset plugin generators (#624)
* feat: add support for appset plugin generators This has one caveat, which likely cannot be fixed until #615 is merged; we're limited to simple string maps for `parameters`, which is only a subset of what the plugin generator supports. In reality pretty much anything can go in as a parameter from the perspective of the plugin generator, but `terraform-plugin-sdk` needs an explicit type. With `terraform-plugin-framework` we can use any as a type, then we can handle the marshaling/unmarshaling appropriately. I don't believe this will be a breaking change in the future since we will be widening the types `parameters` will accept at a later stage. Fixes #620. Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com> * chore: wait for crds to be fully installed Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com> --------- Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
1 parent e54097b commit 71bffe3

File tree

5 files changed

+2445
-140
lines changed

5 files changed

+2445
-140
lines changed

GNUmakefile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,16 @@ testacc_prepare_env:
5151
echo "\n--- Fetch ArgoCD installation manifests\n"
5252
curl https://raw.githubusercontent.com/argoproj/argo-cd/${ARGOCD_VERSION}/manifests/install.yaml > manifests/install/argocd.yml
5353

54+
5455
echo "\n--- Install ArgoCD ${ARGOCD_VERSION}\n"
55-
kustomize build manifests/install | kubectl apply -f - && \
56+
kustomize build manifests/install | kubectl apply -f -
57+
58+
echo "\n--- Wait until CRDs are established\n"
59+
kubectl wait --for=condition=Established crd/applications.argoproj.io --timeout=60s
60+
kubectl wait --for=condition=Established crd/applicationsets.argoproj.io --timeout=60s
61+
kubectl wait --for=condition=Established crd/appprojects.argoproj.io --timeout=60s
62+
63+
echo "\n--- Install ArgoCD test data\n"
5664
kubectl apply -f manifests/testdata/
5765

5866
echo "\n--- Wait for ArgoCD components to be ready...\n"

argocd/resource_argocd_application_set_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,42 @@ func TestAccArgoCDApplicationSet_gitFiles(t *testing.T) {
163163
})
164164
}
165165

166+
func TestAccArgoCDApplicationSet_plugin(t *testing.T) {
167+
resource.ParallelTest(t, resource.TestCase{
168+
PreCheck: func() { testAccPreCheck(t); testAccPreCheckFeatureSupported(t, features.ApplicationSet) },
169+
ProviderFactories: testAccProviders,
170+
Steps: []resource.TestStep{
171+
{
172+
Config: testAccArgoCDApplicationSet_plugin(),
173+
Check: resource.ComposeTestCheckFunc(
174+
resource.TestCheckResourceAttrSet(
175+
"argocd_application_set.plugin",
176+
"metadata.0.uid",
177+
),
178+
resource.TestCheckResourceAttrSet(
179+
"argocd_application_set.plugin",
180+
"spec.0.generator.0.plugin.0.requeue_after_seconds",
181+
),
182+
resource.TestCheckResourceAttrSet(
183+
"argocd_application_set.plugin",
184+
"spec.0.generator.0.plugin.0.config_map_ref",
185+
),
186+
resource.TestCheckResourceAttrSet(
187+
"argocd_application_set.plugin",
188+
"spec.0.generator.0.plugin.0.input.0.parameters.key1",
189+
),
190+
),
191+
},
192+
{
193+
ResourceName: "argocd_application_set.plugin",
194+
ImportState: true,
195+
ImportStateVerify: true,
196+
ImportStateVerifyIgnore: []string{"metadata.0.resource_version"},
197+
},
198+
},
199+
})
200+
}
201+
166202
func TestAccArgoCDApplicationSet_list(t *testing.T) {
167203
resource.ParallelTest(t, resource.TestCase{
168204
PreCheck: func() { testAccPreCheck(t); testAccPreCheckFeatureSupported(t, features.ApplicationSet) },
@@ -1183,6 +1219,52 @@ resource "argocd_application_set" "git_files" {
11831219
}`
11841220
}
11851221

1222+
func testAccArgoCDApplicationSet_plugin() string {
1223+
return `
1224+
resource "argocd_application_set" "plugin" {
1225+
metadata {
1226+
name = "plugin"
1227+
}
1228+
1229+
spec {
1230+
generator {
1231+
plugin {
1232+
config_map_ref = "plugin"
1233+
1234+
input {
1235+
parameters = {
1236+
key1 = "value1"
1237+
}
1238+
}
1239+
1240+
requeue_after_seconds = 30
1241+
}
1242+
}
1243+
1244+
template {
1245+
metadata {
1246+
name = "{{cluster}}-guestbook"
1247+
}
1248+
1249+
spec {
1250+
project = "default"
1251+
1252+
source {
1253+
repo_url = "https://github.com/argoproj/argo-cd.git"
1254+
target_revision = "HEAD"
1255+
path = "applicationset/examples/list-generator/guestbook/{{cluster}}"
1256+
}
1257+
1258+
destination {
1259+
server = "{{url}}"
1260+
namespace = "guestbook"
1261+
}
1262+
}
1263+
}
1264+
}
1265+
}`
1266+
}
1267+
11861268
func testAccArgoCDApplicationSet_list() string {
11871269
return `
11881270
resource "argocd_application_set" "list" {

argocd/schema_application_set.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ func generatorResourceV0(level int) *schema.Resource {
156156
"list": applicationSetListGeneratorSchemaV0(),
157157
"matrix": applicationSetMatrixGeneratorSchemaV0(level),
158158
"merge": applicationSetMergeGeneratorSchemaV0(level),
159+
"plugin": applicationSetPluginGeneratorSchemaV0(),
159160
"pull_request": applicationSetPullRequestGeneratorSchemaV0(),
160161
"scm_provider": applicationSetSCMProviderGeneratorSchemaV0(),
161162
"selector": {
@@ -173,6 +174,7 @@ func generatorResourceV0(level int) *schema.Resource {
173174

174175
return &schema.Resource{
175176
Schema: map[string]*schema.Schema{
177+
"plugin": applicationSetPluginGeneratorSchemaV0(),
176178
"cluster_decision_resource": applicationSetClusterDecisionResourceGeneratorSchemaV0(),
177179
"clusters": applicationSetClustersGeneratorSchemaV0(),
178180
"git": applicationSetGitGeneratorSchemaV0(),
@@ -281,6 +283,57 @@ func applicationSetClusterDecisionResourceGeneratorSchemaV0() *schema.Schema {
281283
}
282284
}
283285

286+
func applicationSetPluginGeneratorSchemaV0() *schema.Schema {
287+
return &schema.Schema{
288+
Type: schema.TypeList,
289+
Description: "[Plugin generators](https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Generators-Plugin/) generates parameters using a custom plugin.",
290+
Optional: true,
291+
Elem: &schema.Resource{
292+
Schema: map[string]*schema.Schema{
293+
"config_map_ref": {
294+
Type: schema.TypeString,
295+
Description: "ConfigMap with the plugin configuration needed to retrieve the data.",
296+
Required: true,
297+
},
298+
"requeue_after_seconds": {
299+
Type: schema.TypeString,
300+
Description: "How often to check for changes (in seconds). Default: 3min.",
301+
Optional: true,
302+
},
303+
"input": {
304+
Type: schema.TypeList,
305+
Description: "The input parameters used for calling the plugin.",
306+
Optional: true,
307+
MaxItems: 1,
308+
Elem: &schema.Resource{
309+
Schema: map[string]*schema.Schema{
310+
"parameters": {
311+
Type: schema.TypeMap,
312+
Description: "Arbitrary key-value pairs which are passed directly as parameters to the plugin. A current limitation is that this cannot fully express the parameters that can be accepted by the plugin generator.",
313+
Required: true,
314+
Elem: &schema.Schema{Type: schema.TypeString},
315+
},
316+
},
317+
},
318+
},
319+
"template": {
320+
Type: schema.TypeList,
321+
Description: "Generator template. Used to override the values of the spec-level template.",
322+
Optional: true,
323+
MaxItems: 1,
324+
Elem: applicationSetTemplateResource(true),
325+
},
326+
"values": {
327+
Type: schema.TypeMap,
328+
Description: "Arbitrary string key-value pairs to pass to the template via the values field of the git generator.",
329+
Optional: true,
330+
Elem: &schema.Schema{Type: schema.TypeString},
331+
},
332+
},
333+
},
334+
}
335+
}
336+
284337
func applicationSetGitGeneratorSchemaV0() *schema.Schema {
285338
return &schema.Schema{
286339
Type: schema.TypeList,

argocd/structure_application_set.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ func expandApplicationSetGenerators(g []interface{}, featureMultipleApplicationS
9595
g, err = expandApplicationSetSCMProviderGenerator(asg[0], featureMultipleApplicationSourcesSupported)
9696
} else if asg, ok = v["pull_request"].([]interface{}); ok && len(asg) > 0 {
9797
g, err = expandApplicationSetPullRequestGeneratorGenerator(asg[0], featureMultipleApplicationSourcesSupported)
98+
} else if asg, ok = v["plugin"].([]interface{}); ok && len(asg) > 0 {
99+
g, err = expandApplicationSetPluginGenerator(asg[0], featureMultipleApplicationSourcesSupported)
98100
}
99101

100102
if err != nil {
@@ -393,6 +395,70 @@ func expandApplicationSetMergeGenerator(mg interface{}, featureMultipleApplicati
393395
return asg, nil
394396
}
395397

398+
func expandApplicationSetPluginGenerator(mg interface{}, featureMultipleApplicationSourcesSupported bool) (*application.ApplicationSetGenerator, error) {
399+
asg := &application.ApplicationSetGenerator{
400+
Plugin: &application.PluginGenerator{},
401+
}
402+
403+
m := mg.(map[string]interface{})
404+
405+
if v, ok := m["input"].([]interface{}); ok && len(v) > 0 {
406+
tmp, err := expandApplicationSetInputParameters(v[0].(map[string]interface{}))
407+
if err != nil {
408+
return nil, err
409+
}
410+
411+
asg.Plugin.Input.Parameters = tmp
412+
}
413+
414+
if v, ok := m["config_map_ref"].(string); ok && v != "" {
415+
asg.Plugin.ConfigMapRef.Name = v
416+
}
417+
418+
if v, ok := m["requeue_after_seconds"].(string); ok && v != "" {
419+
ras, err := convertStringToInt64Pointer(v)
420+
if err != nil {
421+
return nil, fmt.Errorf("failed to convert requeue_after_seconds to *int64: %w", err)
422+
}
423+
424+
asg.Plugin.RequeueAfterSeconds = ras
425+
}
426+
427+
if v, ok := m["template"].([]interface{}); ok && len(v) > 0 {
428+
temp, err := expandApplicationSetTemplate(v[0], featureMultipleApplicationSourcesSupported)
429+
if err != nil {
430+
return nil, err
431+
}
432+
433+
asg.Plugin.Template = temp
434+
}
435+
436+
if v, ok := m["values"]; ok {
437+
asg.Plugin.Values = expandStringMap(v.(map[string]interface{}))
438+
}
439+
440+
return asg, nil
441+
}
442+
443+
func expandApplicationSetInputParameters(m map[string]interface{}) (application.PluginParameters, error) {
444+
params := application.PluginParameters{}
445+
446+
if v, ok := m["parameters"].(map[string]interface{}); ok && len(v) > 0 {
447+
for k, v := range v {
448+
json, err := json.Marshal(v)
449+
if err != nil {
450+
return params, fmt.Errorf("failed to marshal plugin param to json: %w", err)
451+
}
452+
453+
params[k] = apiextensionsv1.JSON{
454+
Raw: json,
455+
}
456+
}
457+
}
458+
459+
return params, nil
460+
}
461+
396462
func expandApplicationSetPullRequestGeneratorGenerator(mg interface{}, featureMultipleApplicationSourcesSupported bool) (*application.ApplicationSetGenerator, error) {
397463
asg := &application.ApplicationSetGenerator{
398464
PullRequest: &application.PullRequestGenerator{},
@@ -1021,6 +1087,13 @@ func flattenGenerator(g application.ApplicationSetGenerator) (map[string]interfa
10211087
generator["scm_provider"] = flattenApplicationSetSCMProviderGenerator(g.SCMProvider)
10221088
} else if g.PullRequest != nil {
10231089
generator["pull_request"] = flattenApplicationSetPullRequestGenerator(g.PullRequest)
1090+
} else if g.Plugin != nil {
1091+
pluginGenerator, err := flattenApplicationSetPluginGenerator(g.Plugin)
1092+
if err != nil {
1093+
return nil, err
1094+
}
1095+
1096+
generator["plugin"] = pluginGenerator
10241097
}
10251098

10261099
if g.Selector != nil {
@@ -1155,6 +1228,44 @@ func flattenApplicationSetMergeGenerator(mg *application.MergeGenerator) ([]map[
11551228
return []map[string]interface{}{g}, nil
11561229
}
11571230

1231+
func flattenApplicationSetPluginGenerator(plg *application.PluginGenerator) ([]map[string]interface{}, error) {
1232+
g := map[string]interface{}{}
1233+
1234+
if plg.Input.Parameters != nil {
1235+
input := map[string]interface{}{}
1236+
parameters := map[string]string{}
1237+
1238+
// TODO: In reality, the parameters map can potentially contain anything, but
1239+
// terraform-plugin-sdk doesn't really support the notion of `any`. We need to
1240+
// improve this once we upgrade to terraform-plugin-framework
1241+
for k, v := range plg.Input.Parameters {
1242+
var str string
1243+
err := json.Unmarshal(v.Raw, &str)
1244+
1245+
if err != nil {
1246+
return nil, err
1247+
}
1248+
1249+
parameters[k] = str
1250+
}
1251+
1252+
input["parameters"] = parameters
1253+
g["input"] = []map[string]interface{}{input}
1254+
}
1255+
1256+
if plg.ConfigMapRef.Name != "" {
1257+
g["config_map_ref"] = plg.ConfigMapRef.Name
1258+
}
1259+
1260+
if plg.RequeueAfterSeconds != nil {
1261+
g["requeue_after_seconds"] = convertInt64PointerToString(plg.RequeueAfterSeconds)
1262+
}
1263+
1264+
g["template"] = flattenApplicationSetTemplate(plg.Template)
1265+
1266+
return []map[string]interface{}{g}, nil
1267+
}
1268+
11581269
func flattenApplicationSetPullRequestGenerator(prg *application.PullRequestGenerator) []map[string]interface{} {
11591270
g := map[string]interface{}{}
11601271

0 commit comments

Comments
 (0)