Skip to main content

Overview

Argo CD allows operators to define custom actions that users can perform on specific resource types. Actions are implemented as Lua scripts and can modify resources or create new ones.
Built-in ActionsArgo CD includes built-in actions like restart for DaemonSet/Deployment/StatefulSet and retry for Argo Rollouts. You can extend these capabilities with custom actions.

Action Types

Argo CD supports two types of resource actions:
Returns the modified source resource. This was the only action type until v2.8 and is still supported.
-- Suspend a CronJob
obj.spec.suspend = true
return obj

Basic Configuration

Define actions in the argocd-cm ConfigMap using the format: resource.customizations.actions.<apiGroup>_<kind>

Simple Action Example

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  resource.customizations.actions.batch_CronJob: |
    discovery.lua: |
      actions = {}
      actions["suspend"] = {["disabled"] = true}
      actions["resume"] = {["disabled"] = true}
      
      local suspend = false
      if obj.spec.suspend ~= nil then
        suspend = obj.spec.suspend
      end
      
      if suspend then
        actions["resume"]["disabled"] = false
      else
        actions["suspend"]["disabled"] = false
      end
      
      return actions
      
    definitions:
    - name: suspend
      action.lua: |
        obj.spec.suspend = true
        return obj
        
    - name: resume
      action.lua: |
        if obj.spec.suspend ~= nil and obj.spec.suspend then
          obj.spec.suspend = false
        end
        return obj

Discovery Script

The discovery.lua script determines which actions are available and whether they’re enabled:
actions = {}

-- Define available actions
actions["restart"] = {}
actions["scale"] = {}
actions["delete-pod"] = {["disabled"] = true}

-- Conditionally enable actions based on resource state
if obj.spec.replicas ~= nil and obj.spec.replicas == 0 then
  actions["restart"]["disabled"] = true
end

if obj.status ~= nil and obj.status.availableReplicas ~= nil then
  actions["delete-pod"]["disabled"] = false
end

return actions
Dynamic ActionsUse the resource’s current state to enable/disable actions dynamically. This provides better UX by only showing relevant actions.

Action Definitions

Each action must have a definition with the action logic:
definitions:
- name: restart
  action.lua: |
    local os = require("os")
    if obj.spec.template.metadata == nil then
      obj.spec.template.metadata = {}
    end
    if obj.spec.template.metadata.annotations == nil then
      obj.spec.template.metadata.annotations = {}
    end
    obj.spec.template.metadata.annotations["kubectl.kubernetes.io/restartedAt"] = os.date("!%Y-%m-%dT%XZ")
    return obj

Customizing Action Appearance

Add icons and display names to your actions:
local actions = {}

actions["create-workflow"] = {
  ["iconClass"] = "fa fa-fw fa-plus",
  ["displayName"] = "Create Workflow"
}

actions["terminate"] = {
  ["iconClass"] = "fa fa-fw fa-stop-circle",
  ["displayName"] = "Terminate Analysis"
}

return actions
Use FontAwesome free icons. The fa-fw class ensures fixed-width display.

Action Parameters

Define parameters that users can provide when executing actions:
actions["scale"] = {
  ["displayName"] = "Scale",
  ["iconClass"] = "fa fa-fw fa-arrows-v"
}

actions["scale"].params = {
  {
    name = "replicas",
    type = "number",
    default = "1"
  }
}

return actions
Access parameters in the action script:
local replicas = tonumber(params.replicas) or 1
obj.spec.replicas = replicas
return obj

Creating New Resources

GitOps DepartureCreating resources via the UI departs from GitOps principles. Use this sparingly and only for resources not part of the desired state.

Creating Child Resources

Set ownerReference for Kubernetes garbage collection:
-- Create a Job from a CronJob
ownerRef = {}
ownerRef.apiVersion = obj.apiVersion
ownerRef.kind = obj.kind
ownerRef.name = obj.metadata.name
ownerRef.uid = obj.metadata.uid

job = {}
job.apiVersion = "batch/v1"
job.kind = "Job"
job.metadata = {}
job.metadata.name = obj.metadata.name .. "-manual-" .. os.time()
job.metadata.namespace = obj.metadata.namespace
job.metadata.ownerReferences = {ownerRef}
job.spec = obj.spec.jobTemplate.spec

result = {}
result[1] = {operation = "create", resource = job}
return result

Creating Independent Resources

1

Add Tracking Label

Copy the Argo CD tracking label so the App recognizes the resource:
newObj.metadata.labels = {}
newObj.metadata.labels["app.kubernetes.io/instance"] = 
  obj.metadata.labels["app.kubernetes.io/instance"]
2

Prevent Auto-Prune

Set annotation to prevent deletion with auto-prune:
newObj.metadata.annotations = {}
newObj.metadata.annotations["argocd.argoproj.io/sync-options"] = 
  "Prune=false"
3

Keep App Synced (Optional)

Prevent out-of-sync status:
newObj.metadata.annotations["argocd.argoproj.io/compare-options"] = 
  "IgnoreExtraneous"

Complete Creation Example

resource.customizations.actions.ConfigMap: |
  discovery.lua: |
    actions = {}
    actions["create-backup"] = {
      ["iconClass"] = "fa fa-fw fa-save",
      ["displayName"] = "Create Backup"
    }
    return actions
    
  definitions:
  - name: create-backup
    action.lua: |
      local os = require("os")
      
      -- Create backup ConfigMap
      backup = {}
      backup.apiVersion = "v1"
      backup.kind = "ConfigMap"
      backup.metadata = {}
      backup.metadata.name = obj.metadata.name .. "-backup-" .. os.time()
      backup.metadata.namespace = obj.metadata.namespace
      backup.metadata.labels = {}
      backup.metadata.labels["app.kubernetes.io/instance"] = 
        obj.metadata.labels["app.kubernetes.io/instance"]
      backup.metadata.labels["backup-of"] = obj.metadata.name
      backup.metadata.annotations = {}
      backup.metadata.annotations["argocd.argoproj.io/sync-options"] = 
        "Prune=false"
      backup.metadata.annotations["argocd.argoproj.io/compare-options"] = 
        "IgnoreExtraneous"
      backup.data = obj.data
      
      -- Also update original with backup timestamp
      if obj.metadata.annotations == nil then
        obj.metadata.annotations = {}
      end
      obj.metadata.annotations["last-backup"] = os.date("!%Y-%m-%dT%XZ")
      
      result = {}
      result[1] = {operation = "create", resource = backup}
      result[2] = {operation = "patch", resource = obj}
      return result

Merging with Built-in Actions

By default, custom actions override built-in actions. To keep built-in actions:
resource.customizations.actions.argoproj.io_Rollout: |
  mergeBuiltinActions: true
  discovery.lua: |
    actions = {}
    actions["custom-action"] = {}
    return actions
  definitions:
  - name: custom-action
    action.lua: |
      return obj
Custom actions have precedence over built-in actions when names conflict.

Contributing Actions

Contribute actions to the Argo CD repository:
argo-cd/
└── resource_customizations/
    └── your.crd.group.io/
        └── MyKind/
            └── actions/
                ├── action_test.yaml
                ├── discovery.lua
                ├── testdata/
                │   ├── running.yaml
                │   └── terminated.yaml
                └── <action-name>/
                    └── action.lua

Test File Format

discoveryTests:
- inputPath: testdata/running.yaml
  result:
  - name: terminate
    disabled: false
  - name: suspend  
    disabled: true
    
- inputPath: testdata/terminated.yaml
  result:
  - name: terminate
    disabled: true

actionTests:
- action: terminate
  inputPath: testdata/running.yaml
  expectedOutputPath: testdata/running_terminated.yaml
Run tests:
go test -v ./resource_customizations/.../actions/

RBAC for Actions

Control access to actions using RBAC:
p, role:action-runner, action/*, *, */*, allow
p, role:limited-user, action/apps/Deployment/restart, *, */*, allow
p, role:limited-user, action/apps/Deployment/scale, *, */*, deny
See the RBAC documentation for details.

Best Practices

Always validate user input and resource state:
if obj.spec.replicas == nil then
  error("Resource does not support scaling")
end

local replicas = tonumber(params.replicas)
if replicas == nil or replicas < 0 then
  error("Invalid replica count")
end
Return meaningful messages on errors:
if obj.status.phase == "Failed" then
  error("Cannot restart a failed job. Delete and recreate instead.")
end
Create comprehensive tests covering:
  • Normal operation
  • Edge cases
  • Error conditions
  • Various resource states
Choose clear, action-oriented names:
  • restart, scale, create-backup
  • do-thing, action1, fix

Security Considerations

Resource PermissionsWhen creating resources:
  • New resources must be permitted at the AppProject level
  • Users need appropriate RBAC permissions
  • Consider the security implications of creating resources outside GitOps

Example: Scale Deployment

resource.customizations.actions.apps_Deployment: |
  discovery.lua: |
    actions = {}
    actions["scale"] = {
      ["iconClass"] = "fa fa-fw fa-arrows-v",
      ["displayName"] = "Scale"
    }
    
    -- Add parameter definition
    actions["scale"].params = {
      {
        name = "replicas",
        type = "number",
        default = tostring(obj.spec.replicas or 1)
      }
    }
    
    -- Disable if already scaling
    if obj.status ~= nil and obj.status.replicas ~= obj.status.updatedReplicas then
      actions["scale"]["disabled"] = true
    end
    
    return actions
    
  definitions:
  - name: scale  
    action.lua: |
      local replicas = tonumber(params.replicas)
      
      if replicas == nil or replicas < 0 then
        error("Invalid replica count: " .. params.replicas)
      end
      
      if replicas > 100 then
        error("Replica count exceeds maximum allowed (100)")
      end
      
      obj.spec.replicas = replicas
      return obj

Next Steps

Custom Health Checks

Define custom health checks for resources

UI Extensions

Extend the Argo CD web interface

Built-in Actions

Browse built-in resource actions

RBAC

Configure action permissions