Deploy an Argo CD application using the Go SDK

更新时间:
复制 MD 格式

ACK One GitOps is built on managed Argo CD and is fully compatible with Argo CD APIs, including the Application API. Compared with open source Argo CD, ACK One GitOps offers advantages such as multi-cluster distribution, multi-user permission management, and out-of-the-box, O&M-free use. To integrate ACK One GitOps into your application release pipeline, use the Argo CD Go SDK to create, delete, and sync Applications programmatically. This topic demonstrates how to do this using Argo CD v2.9.3.

Prerequisites

Before you begin, ensure that you have:

  • An ACK One fleet instance with associated clusters

  • kubectl installed and configured

  • The Argo CD CLI (argocd) installed

  • Go 1.19 or later

Step 1: Get the cluster Server or Name

Each Argo CD Application targets a specific cluster identified by either its Server (API endpoint URL) or Name (a human-readable identifier). Specify one of these values in your Application spec.

Name uses the format <cluster-id>-<cluster-name> by default and is easier to read than a raw API endpoint.

  1. Get the kubeconfig of your fleet instance from the ACK One console and connect to it with kubectl. For details, see Obtain the kubeconfig of a cluster and use kubectl to connect to the cluster.

  2. Run one of the following commands to list all registered clusters. You only need one of these values.

    • List Name values: ``shell kubectl get secret -nargocd -l argocd.argoproj.io/secret-type=cluster |awk 'NR>1 {print $1}'|xargs -I {} sh -c 'kubectl get secret -nargocd {} -ojsonpath="{.data.name}"|base64 -d; echo' ``

    • List Server values: ``shell kubectl get secret -nargocd -l argocd.argoproj.io/secret-type=cluster |awk 'NR>1 {print $1}'|xargs -I {} sh -c 'kubectl get secret -nargocd {} -ojsonpath="{.data.server}"|base64 -d; echo' ``

Step 2: Create a local user and generate a token

The Go SDK authenticates with Argo CD using a bearer token. Create a local user in the fleet instance and generate a token for it.

Create a local user

  1. Edit the argocd-cm ConfigMap to add a local user named localuser1:

    kubectl edit cm argocd-cm -n argocd

    Add the following entries to the data section. The login capability allows UI and CLI login; apiKey allows generating API tokens.

    data:
      accounts.localuser1: login,apiKey   # Allow login and API token generation
      accounts.localuser1.enabled: "true" # Enable the user
  2. Verify the user was created:

    argocd account list

    Expected output:

    NAME        ENABLED  CAPABILITIES
    admin       true     login
    localuser1  true     login,apiKey

Grant RBAC permissions

Edit the argocd-rbac-cm ConfigMap to assign a role to the new user:

kubectl edit cm argocd-rbac-cm -n argocd

Add the following entry to policy.csv. Leave existing entries unchanged.

data:
  policy.csv: |
    g, "14***01", role:admin      # Existing entry — do not modify
    g, localuser1, role:admin     # Grant localuser1 the admin role (read/write on applications, clusters, and projects)
  scopes: '[uid]'
Important

Do not modify existing entries in argocd-rbac-cm. Only append new lines.

For details on Argo CD Role-Based Access Control (RBAC) configuration, see Configure Argo CD RBAC for a user.

Generate a token

Argo CD CLI

Using the Argo CD CLI:

# Set a password for localuser1
argocd account update-password \
  --account localuser1 \
  --current-password <admin-password> \
  --new-password <localuser1-password>

# Generate an API token
argocd account generate-token --account localuser1
eyJhb......

Argo CD console

Using the Argo CD console:

  1. Log in to the ACK One console. In the left-side navigation pane, choose Fleet > Multi-cluster Applications.

  2. On the Multi-cluster GitOps page, click the Dingtalk_20231226104633.jpg icon next to the fleet name and select the target fleet. Then click GitOps Console and log in.

  3. In the left navigation pane, choose Settings > Accounts. Click localuser1.

  4. In the Tokens section, click Generate New and save the token.

Step 3: Run the sample code

The sample consists of two files: a main entry point and an application management client.

Main entry point

Replace <argocd-server-lb-ip> with the load balancer IP of the Argo CD server. Get it by running:

kubectl get svc -nargocd argocd-server -ojsonpath='{.status.loadBalancer.ingress[0].ip}'

Replace local user token with the token from Step 2.

package main

import (
	"fmt"
)

func main() {
	conn := &Connection{
		Address: "<argocd-server-lb-ip>",
		Token:   "local user token",
	}
	argoCDClient, err := NewArgoCDClient(conn)
	if err != nil {
		panic(fmt.Sprintf("Failed to create argocd client: %v\n", err))
	}

	// Uncomment to create an application
	//if err := argoCDClient.CreateApplication(false, true); err != nil {
	//	panic(fmt.Sprintf("Failed to create application: %v\n", err))
	//}

	if err := argoCDClient.SyncApplication("argocd", "demo-argocd-app-manual", "default"); err != nil {
		panic(fmt.Sprintf("Failed to sync application: %v\n", err))
	}
}

Application management client

This client is built on the Argo CD apiclient package. Replace ClusterName, ClusterServer, and GitRepoURL with your values.

All five operations — Create, Sync, Get, Update, and Delete — use the same ArgoCDClient struct initialized from a Connection.

package main

import (
	"context"
	"fmt"
	"github.com/argoproj/argo-cd/v2/pkg/apiclient"
	"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
	"github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
	"github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
	argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	GitRepoURL = "https://github.com/AliyunContainerService/gitops-demo.git"

	// Use Server (API endpoint URL) or Name — not both.
	// Name format: <cluster-id>-<cluster-name>, e.g., cc0e3a41fe49c4a7bbcd5a1f0xxxxx-test
	ClusterServer = "https://172.16.xx.xx:6443"
	ClusterName   = "cc0e3a41fe49c4a7bbcd5a1f0xxxxx-test"
)

type ArgoCDClient struct {
	projectClient project.ProjectServiceClient
	clusterClient cluster.ClusterServiceClient
	appClient     application.ApplicationServiceClient
}

type Connection struct {
	Address string
	Token   string
}

func NewArgoCDClient(conn *Connection) (*ArgoCDClient, error) {
	argocdApiClient, err := apiclient.NewClient(&apiclient.ClientOptions{
		ServerAddr: fmt.Sprintf(conn.Address),
		Insecure:   true,
		AuthToken:  conn.Token,
	})
	if err != nil {
		return nil, err
	}

	_, appClient, err := argocdApiClient.NewApplicationClient()
	if err != nil {
		return nil, err
	}

	_, projectClient, err := argocdApiClient.NewProjectClient()
	if err != nil {
		return nil, err
	}

	_, clusterClient, err := argocdApiClient.NewClusterClient()
	if err != nil {
		return nil, err
	}

	return &ArgoCDClient{
		projectClient: projectClient,
		clusterClient: clusterClient,
		appClient:     appClient,
	}, nil
}

// CreateApplication creates an Argo CD Application.
// upsert: if true, overwrites an existing Application with the same name even if the spec differs.
// validate: if true, validates the repository and destination cluster before creating.
func (c *ArgoCDClient) CreateApplication(upsert, validate bool) error {
	app := &argoappv1.Application{
		ObjectMeta: v1.ObjectMeta{
			Name:      "demo-argocd-app-manual",
			Namespace: "argocd",
		},
		Spec: argoappv1.ApplicationSpec{
			Project: "default", // Argo CD project name; "default" is the built-in project
			Source: &argoappv1.ApplicationSource{
				RepoURL:        GitRepoURL,
				Path:           "manifests/helm/echo-server", // Path to the Helm chart or manifests in the repo
				TargetRevision: "HEAD",                        // Git ref: branch, tag, or commit SHA
			},
			Destination: argoappv1.ApplicationDestination{
				// Specify either Name or Server — not both.
				// Server: ClusterServer,
				Name:      ClusterName,
				Namespace: "echo-server-demo2", // Target namespace on the destination cluster
			},
			SyncPolicy: &argoappv1.SyncPolicy{
				SyncOptions: argoappv1.SyncOptions{
					"CreateNamespace=true", // Create the destination namespace if it does not exist
				},
			},
		},
	}

	if _, err := c.appClient.Create(context.Background(), &application.ApplicationCreateRequest{
		Application: app,
		Upsert:      &upsert,
		Validate:    &validate,
	}); err != nil {
		return err
	}

	return nil
}

// SyncApplication triggers a sync for the specified Application.
// For all available sync parameters, run: argocd app sync --help
func (c *ArgoCDClient) SyncApplication(namespace, name, project string) error {
	if _, err := c.appClient.Sync(context.Background(), &application.ApplicationSyncRequest{
		Name:         &name,
		AppNamespace: &namespace,
		Revision:     nil,
		//DryRun:        nil,
		//Prune:         nil,
		//Strategy:      nil,
		//Resources:     nil,
		//Manifests:     nil,
		//Infos:         nil,
		//RetryStrategy: nil,
		//SyncOptions: syncOptionsFactory,
		Project: &project,
	}); err != nil {
		return err
	}

	return nil
}

// GetApplication retrieves the details of an Application.
func (c *ArgoCDClient) GetApplication(namespace, name string) (*argoappv1.Application, error) {
	if app, err := c.appClient.Get(context.Background(), &application.ApplicationQuery{
		Name:         &name,
		AppNamespace: &namespace,
	}); err != nil {
		return nil, err
	} else {
		return app, nil
	}
}

// DeleteApplication deletes an Application.
func (c *ArgoCDClient) DeleteApplication(namespace, name string) error {
	if _, err := c.appClient.Delete(context.Background(), &application.ApplicationDeleteRequest{
		Name:         &name,
		AppNamespace: &namespace,
	}); err != nil {
		return err
	}
	return nil
}

// UpdateApplication updates an existing Application.
func (c *ArgoCDClient) UpdateApplication(newApp *argoappv1.Application, validate bool, project string) error {
	if _, err := c.appClient.Update(context.Background(), &application.ApplicationUpdateRequest{
		Application: newApp,
		Validate:    &validate,
		Project:     &project,
	}); err != nil {
		return err
	}
	return nil
}

go.mod configuration

When importing Argo CD Go packages, you may encounter an unknown revision v0.0.0 error. This happens because Argo CD depends on Kubernetes packages whose go.mod files reference v0.0.0 pseudo-versions.

Fix this by adding a replace section to your go.mod. The Kubernetes package versions in replace must match those used by your Argo CD version. To find the correct versions for any Argo CD release, open the Argo CD repository, switch to the target release tag, and copy the replace section from its go.mod.

For more details, see the community guide Importing Argo CD go packages.

The following go.mod is configured for Argo CD v2.9.3, which requires all Kubernetes packages at v0.24.17 (except k8s.io/apimachinery, which uses v0.24.1).

module awesomeProject1

go 1.19

require (
	github.com/argoproj/argo-cd/v2 v2.9.3
	k8s.io/apimachinery v0.24.17
	sigs.k8s.io/controller-runtime v0.11.0
)

require (
	cloud.google.com/go/compute v1.20.1 // indirect
	cloud.google.com/go/compute/metadata v0.2.3 // indirect
	dario.cat/mergo v1.0.0 // indirect
	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
	github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect
	github.com/Masterminds/semver/v3 v3.2.1 // indirect
	github.com/Microsoft/go-winio v0.6.1 // indirect
	github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
	github.com/argoproj/gitops-engine v0.7.1-0.20230906152414-b0fffe419a0f // indirect
	github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 // indirect
	github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect
	github.com/bombsimon/logrusr/v2 v2.0.1 // indirect
	github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 // indirect
	github.com/cespare/xxhash/v2 v2.2.0 // indirect
	github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect
	github.com/cloudflare/circl v1.3.3 // indirect
	github.com/coreos/go-oidc/v3 v3.6.0 // indirect
	github.com/cyphar/filepath-securejoin v0.2.4 // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
	github.com/docker/distribution v2.8.2+incompatible // indirect
	github.com/emicklei/go-restful/v3 v3.8.0 // indirect
	github.com/emirpasic/gods v1.18.1 // indirect
	github.com/evanphx/json-patch v5.6.0+incompatible // indirect
	github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
	github.com/fatih/camelcase v1.0.0 // indirect
	github.com/felixge/httpsnoop v1.0.3 // indirect
	github.com/fvbommel/sortorder v1.0.1 // indirect
	github.com/go-errors/errors v1.4.2 // indirect
	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
	github.com/go-git/go-billy/v5 v5.5.0 // indirect
	github.com/go-git/go-git/v5 v5.11.0 // indirect
	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
	github.com/go-logr/logr v1.2.4 // indirect
	github.com/go-logr/stdr v1.2.2 // indirect
	github.com/go-openapi/jsonpointer v0.19.5 // indirect
	github.com/go-openapi/jsonreference v0.20.0 // indirect
	github.com/go-openapi/swag v0.22.3 // indirect
	github.com/go-redis/cache/v9 v9.0.0 // indirect
	github.com/gobwas/glob v0.2.3 // indirect
	github.com/gogo/protobuf v1.3.2 // indirect
	github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
	github.com/golang/protobuf v1.5.3 // indirect
	github.com/google/btree v1.1.2 // indirect
	github.com/google/gnostic v0.6.9 // indirect
	github.com/google/go-cmp v0.6.0 // indirect
	github.com/google/go-github/v53 v53.2.0 // indirect
	github.com/google/go-querystring v1.1.0 // indirect
	github.com/google/gofuzz v1.2.0 // indirect
	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
	github.com/google/uuid v1.3.0 // indirect
	github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
	github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
	github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
	github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
	github.com/imdario/mergo v0.3.16 // indirect
	github.com/inconshreveable/mousetrap v1.1.0 // indirect
	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
	github.com/jonboulle/clockwork v0.2.2 // indirect
	github.com/josharian/intern v1.0.0 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
	github.com/kevinburke/ssh_config v1.2.0 // indirect
	github.com/klauspost/compress v1.16.5 // indirect
	github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
	github.com/mailru/easyjson v0.7.7 // indirect
	github.com/mitchellh/go-wordwrap v1.0.0 // indirect
	github.com/moby/spdystream v0.2.0 // indirect
	github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
	github.com/opencontainers/go-digest v1.0.0 // indirect
	github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
	github.com/pjbgf/sha1cd v0.3.0 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/r3labs/diff v1.1.0 // indirect
	github.com/redis/go-redis/v9 v9.0.5 // indirect
	github.com/robfig/cron/v3 v3.0.1 // indirect
	github.com/russross/blackfriday v1.6.0 // indirect
	github.com/sergi/go-diff v1.1.0 // indirect
	github.com/sirupsen/logrus v1.9.3 // indirect
	github.com/skeema/knownhosts v1.2.1 // indirect
	github.com/spf13/cobra v1.7.0 // indirect
	github.com/spf13/pflag v1.0.5 // indirect
	github.com/vmihailenco/go-tinylfu v0.2.2 // indirect
	github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
	github.com/xanzy/ssh-agent v0.3.3 // indirect
	github.com/xlab/treeprint v1.1.0 // indirect
	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0 // indirect
	go.opentelemetry.io/otel v1.16.0 // indirect
	go.opentelemetry.io/otel/metric v1.16.0 // indirect
	go.opentelemetry.io/otel/trace v1.16.0 // indirect
	go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect
	golang.org/x/crypto v0.16.0 // indirect
	golang.org/x/mod v0.12.0 // indirect
	golang.org/x/net v0.19.0 // indirect
	golang.org/x/oauth2 v0.11.0 // indirect
	golang.org/x/sync v0.3.0 // indirect
	golang.org/x/sys v0.15.0 // indirect
	golang.org/x/term v0.15.0 // indirect
	golang.org/x/text v0.14.0 // indirect
	golang.org/x/time v0.3.0 // indirect
	golang.org/x/tools v0.13.0 // indirect
	google.golang.org/appengine v1.6.7 // indirect
	google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
	google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
	google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
	google.golang.org/grpc v1.56.2 // indirect
	google.golang.org/protobuf v1.31.0 // indirect
	gopkg.in/inf.v0 v0.9.1 // indirect
	gopkg.in/warnings.v0 v0.1.2 // indirect
	gopkg.in/yaml.v2 v2.4.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
	k8s.io/api v0.24.17 // indirect
	k8s.io/apiextensions-apiserver v0.24.2 // indirect
	k8s.io/apiserver v0.24.17 // indirect
	k8s.io/cli-runtime v0.24.17 // indirect
	k8s.io/client-go v0.24.17 // indirect
	k8s.io/component-base v0.24.17 // indirect
	k8s.io/component-helpers v0.24.17 // indirect
	k8s.io/klog/v2 v2.70.1 // indirect
	k8s.io/kube-aggregator v0.24.2 // indirect
	k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 // indirect
	k8s.io/kubectl v0.24.2 // indirect
	k8s.io/kubernetes v1.24.17 // indirect
	k8s.io/utils v0.0.0-20220706174534-f6158b442e7c // indirect
	oras.land/oras-go/v2 v2.3.0 // indirect
	sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 // indirect
	sigs.k8s.io/kustomize/api v0.11.5 // indirect
	sigs.k8s.io/kustomize/kyaml v0.13.7 // indirect
	sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect
	sigs.k8s.io/yaml v1.3.0 // indirect
)

replace (
	// https://github.com/golang/go/issues/33546#issuecomment-519656923
	github.com/go-check/check => github.com/go-check/check v0.0.0-20180628173108-788fd7840127

	github.com/golang/protobuf => github.com/golang/protobuf v1.4.2
	github.com/gorilla/websocket => github.com/gorilla/websocket v1.4.2
	github.com/grpc-ecosystem/grpc-gateway => github.com/grpc-ecosystem/grpc-gateway v1.16.0
	github.com/improbable-eng/grpc-web => github.com/improbable-eng/grpc-web v0.0.0-20181111100011-16092bd1d58a

	// Avoid CVE-2022-28948
	gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1

	// https://github.com/kubernetes/kubernetes/issues/79384#issuecomment-505627280
	k8s.io/api => k8s.io/api v0.24.17
	k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.24.17
	k8s.io/apimachinery => k8s.io/apimachinery v0.24.1
	k8s.io/apiserver => k8s.io/apiserver v0.24.17
	k8s.io/cli-runtime => k8s.io/cli-runtime v0.24.17
	k8s.io/client-go => k8s.io/client-go v0.24.17
	k8s.io/cloud-provider => k8s.io/cloud-provider v0.24.17
	k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.24.17
	k8s.io/code-generator => k8s.io/code-generator v0.24.17
	k8s.io/component-base => k8s.io/component-base v0.24.17
	k8s.io/component-helpers => k8s.io/component-helpers v0.24.17
	k8s.io/controller-manager => k8s.io/controller-manager v0.24.17
	k8s.io/cri-api => k8s.io/cri-api v0.24.17
	k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.24.17
	k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.24.17
	k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.24.17
	k8s.io/kube-proxy => k8s.io/kube-proxy v0.24.17
	k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.24.17
	k8s.io/kubectl => k8s.io/kubectl v0.24.17
	k8s.io/kubelet => k8s.io/kubelet v0.24.17
	k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.24.17
	k8s.io/metrics => k8s.io/metrics v0.24.17
	k8s.io/mount-utils => k8s.io/mount-utils v0.24.17
	k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.24.17
	k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.24.17
)

What's next