Skip to content

Commit

Permalink
feat: support managing Application CRD using K8S client (#140) (#144)
Browse files Browse the repository at this point in the history
* feat: support managing Application CRD using K8S client (#140)

Signed-off-by: Alexander Matyushentsev <[email protected]>

* apply review notes

Signed-off-by: Alexander Matyushentsev <[email protected]>
  • Loading branch information
Alexander Matyushentsev authored Jan 25, 2021
1 parent 74e9d3f commit 707891a
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 89 deletions.
43 changes: 29 additions & 14 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,24 @@ const defaultArgoCDServerAddr = "argocd-server.argocd"
// Default path to registry configuration
const defaultRegistriesConfPath = "/app/config/registries.conf"

const applicationsAPIKindK8S = "kubernetes"
const applicationsAPIKindArgoCD = "argocd"

// ImageUpdaterConfig contains global configuration and required runtime data
type ImageUpdaterConfig struct {
ClientOpts argocd.ClientOptions
ArgocdNamespace string
DryRun bool
CheckInterval time.Duration
ArgoClient argocd.ArgoCD
LogLevel string
KubeClient *kube.KubernetesClient
MaxConcurrency int
HealthPort int
MetricsPort int
RegistriesConf string
AppNamePatterns []string
ApplicationsAPIKind string
ClientOpts argocd.ClientOptions
ArgocdNamespace string
DryRun bool
CheckInterval time.Duration
ArgoClient argocd.ArgoCD
LogLevel string
KubeClient *kube.KubernetesClient
MaxConcurrency int
HealthPort int
MetricsPort int
RegistriesConf string
AppNamePatterns []string
}

// warmupImageCache performs a cache warm-up, which is basically one cycle of
Expand All @@ -71,7 +75,16 @@ func warmupImageCache(cfg *ImageUpdaterConfig) error {
// Main loop for argocd-image-controller
func runImageUpdater(cfg *ImageUpdaterConfig, warmUp bool) (argocd.ImageUpdaterResult, error) {
result := argocd.ImageUpdaterResult{}
argoClient, err := argocd.NewClient(&cfg.ClientOpts)
var err error
var argoClient argocd.ArgoCD
switch cfg.ApplicationsAPIKind {
case applicationsAPIKindK8S:
argoClient, err = argocd.NewK8SClient(cfg.KubeClient)
case applicationsAPIKindArgoCD:
argoClient, err = argocd.NewAPIClient(&cfg.ClientOpts)
default:
return argocd.ImageUpdaterResult{}, fmt.Errorf("application api '%s' is not supported", cfg.ApplicationsAPIKind)
}
if err != nil {
return result, err
}
Expand Down Expand Up @@ -423,7 +436,8 @@ func newRunCommand() *cobra.Command {
cfg.ClientOpts.AuthToken = token
}

log.Infof("ArgoCD configuration: [server=%s, auth_token=%v, insecure=%v, grpc_web=%v, plaintext=%v]",
log.Infof("ArgoCD configuration: [apiKind=%s, server=%s, auth_token=%v, insecure=%v, grpc_web=%v, plaintext=%v]",
cfg.ApplicationsAPIKind,
cfg.ClientOpts.ServerAddr,
cfg.ClientOpts.AuthToken != "",
cfg.ClientOpts.Insecure,
Expand Down Expand Up @@ -496,6 +510,7 @@ func newRunCommand() *cobra.Command {
},
}

runCmd.Flags().StringVar(&cfg.ApplicationsAPIKind, "applications-api", env.GetStringVal("APPLICATIONS_API", applicationsAPIKindK8S), "API kind that is used to manage Argo CD applications ('kubernetes' or 'argocd')")
runCmd.Flags().StringVar(&cfg.ClientOpts.ServerAddr, "argocd-server-addr", env.GetStringVal("ARGOCD_SERVER", ""), "address of ArgoCD API server")
runCmd.Flags().BoolVar(&cfg.ClientOpts.GRPCWeb, "argocd-grpc-web", env.GetBoolVal("ARGOCD_GRPC_WEB", false), "use grpc-web for connection to ArgoCD")
runCmd.Flags().BoolVar(&cfg.ClientOpts.Insecure, "argocd-insecure", env.GetBoolVal("ARGOCD_INSECURE", false), "(INSECURE) ignore invalid TLS certs for ArgoCD server")
Expand Down
Empty file removed docs/configuration/basics.md
Empty file.
142 changes: 74 additions & 68 deletions docs/install/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

## Runtime environment

It is recommend to run Argo CD Image Updater in the same Kubernetes cluster that
It is recommended to run Argo CD Image Updater in the same Kubernetes namespace cluster that
Argo CD is running in, however, this is not a requirement. In fact, it is not
even a requirement to run Argo CD Image Updater within a Kubernetes cluster or
with access to any Kubernetes cluster at all.
with access to any Kubernetes cluster at all.

However, some features might not work without accessing Kubernetes.

Expand Down Expand Up @@ -71,9 +71,68 @@ It is recommended that you read about core updating and image concepts in the
[documentation](../../configuration/images/)
before using this command.

## Prerequisites
## Installing as Kubernetes workload in Argo CD namespace

Argo CD Image Updater will need access to the API of your Argo CD installation.
The most straightforward way to run the image updater is to install is a Kubernetes workload into the namespace where
Argo CD is running. Don't worry, without any configuration, it will not start messing with your workloads yet.

!!!note
We also provide a Kustomize base in addition to the plain Kubernetes YAML
manifests. You can use it as remote base and create overlays with your
configuration on top of it. The remote base's URL is
`https://github.com/argoproj-labs/argocd-image-updater/manifests/base`

### Apply the installation manifests

```shell
kubectl apply -n argocd -f manifests/install.yaml
```

!!!note "A word on high availability"
It is not advised to run multiple replicas of the same Argo CD Image Updater
instance. Just leave the number of replicas at 1, otherwise weird side
effects could occur.

### Minimum mandatory configuration

Even if you don't plan to use private registries, you will need to configure at
least an empty `registries.conf`, because the `argocd-image-updater` pod will
volume mount this entry from the config map.

Edit the `argocd-image-updater-config` ConfigMap and add the following entry:

```yaml
data:
registries.conf: ""
```
Without this entry, the `argocd-image-updater` pod will not be able to start.

If you are going to use private container registries, this is also a good time
to
[configure them](../../configuration/registries/#configuring-a-custom-container-registry)

### Configure the desired log level

While this step is optional, we recommend to set the log level explicitly.
During your first steps with the Argo CD Image Updater, a more verbose logging
can help greatly in troubleshooting things.

Edit the `argocd-image-updater-config` ConfigMap and add the following keys
(the values are dependent upon your environment)

```yaml
data:
# log.level can be one of trace, debug, info, warn or error
log.level: debug
```

If you omit the `log.level` setting, the default `info` level will be used.

## Connect using Argo CD API Server

If you unable to install Argo CD Image Updater into the same Kubernetes cluster you might
configure it to use API of your Argo CD installation.
If you chose to install the Argo CD Image Updater outside of the cluster where
Argo CD is running in, the API must be exposed externally (i.e. using Ingress).
If you have network policies in place, make sure that Argo CD Image Updater will
Expand Down Expand Up @@ -128,70 +187,6 @@ apps, however.
Put the RBAC permissions to Argo CD's `argocd-rbac-cm` ConfigMap and Argo CD will
pick them up automatically.

## Installing as Kubernetes workload

Installation is straight-forward. Don't worry, without any configuration, it
will not start messing with your workloads yet.

!!!note
We also provide a Kustomize base in addition to the plain Kubernetes YAML
manifests. You can use it as remote base and create overlays with your
configuration on top of it. The remote base's URL is
`https://github.com/argoproj-labs/argocd-image-updater/manifests/base`

### Create a dedicated namespace for Argo CD Image Updater

```shell
kubectl create ns argocd-image-updater
```

### Apply the installation manifests

```shell
kubectl apply -n argocd-image-updater -f manifests/install.yaml
```

!!!note "A word on high availability"
It is not advised to run multiple replicas of the same Argo CD Image Updater
instance. Just leave the number of replicas at 1, otherwise weird side
effects could occur.

### Minimum mandatory configuration

Even if you don't plan to use private registries, you will need to configure at
least an empty `registries.conf`, because the `argocd-image-updater` pod will
volume mount this entry from the config map.

Edit the `argocd-image-updater-config` ConfigMap and add the following entry:

```yaml
data:
registries.conf: ""
```
Without this entry, the `argocd-image-updater` pod will not be able to start.

If you are going to use private container registries, this is also a good time
to
[configure them](../../configuration/registries/#configuring-a-custom-container-registry)

### Configure the desired log level

While this step is optional, we recommend to set the log level explicitly.
During your first steps with the Argo CD Image Updater, a more verbose logging
can help greatly in troubleshooting things.

Edit the `argocd-image-updater-config` ConfigMap and add the following keys
(the values are dependent upon your environment)

```yaml
data:
# log.level can be one of trace, debug, info, warn or error
log.level: debug
```

If you omit the `log.level` setting, the default `info` level will be used.

### Configure Argo CD endpoint

If you run Argo CD Image Updater in another cluster than Argo CD, or if your
Expand All @@ -204,6 +199,7 @@ Edit the `argocd-image-updater-config` ConfigMap and add the following keys

```yaml
data:
applications_api: argocd
# The address of Argo CD API endpoint - defaults to argocd-server.argocd
argocd.server_addr: <FQDN or IP of your Argo CD server>
# Whether to use GRPC-web protocol instead of GRPC over HTTP/2
Expand Down Expand Up @@ -260,6 +256,16 @@ Grab the binary (it does not have any external dependencies) and run:
export ARGOCD_TOKEN=<yourtoken>
./argocd-image-updater run \
--kubeconfig ~/.kube/config
--once
```

or use `--applications-api` flag if you prefer to connect using Argo CD API

```bash
export ARGOCD_TOKEN=<yourtoken>
./argocd-image-updater run \
--kubeconfig ~/.kube/config
--applications-api argocd
--argocd-server-addr argo-cd.example.com
--once
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ spec:
image: argoprojlabs/argocd-image-updater:latest
imagePullPolicy: Always
env:
- name: APPLICATIONS_API
valueFrom:
configMapKeyRef:
name: argocd-image-updater-config
key: applications_api
optional: true
- name: ARGOCD_GRPC_WEB
valueFrom:
configMapKeyRef:
Expand Down
9 changes: 9 additions & 0 deletions manifests/base/rbac/argocd-image-updater-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ rules:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
- applications
verbs:
- get
- list
- update
- patch
15 changes: 15 additions & 0 deletions manifests/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ rules:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
- applications
verbs:
- get
- list
- update
- patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
Expand Down Expand Up @@ -80,6 +89,12 @@ spec:
- /usr/local/bin/argocd-image-updater
- run
env:
- name: APPLICATIONS_API
valueFrom:
configMapKeyRef:
key: applications_api
name: argocd-image-updater-config
optional: true
- name: ARGOCD_GRPC_WEB
valueFrom:
configMapKeyRef:
Expand Down
50 changes: 48 additions & 2 deletions pkg/argocd/argocd.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,60 @@ import (

"github.com/argoproj-labs/argocd-image-updater/pkg/common"
"github.com/argoproj-labs/argocd-image-updater/pkg/image"
"github.com/argoproj-labs/argocd-image-updater/pkg/kube"
"github.com/argoproj-labs/argocd-image-updater/pkg/log"
"github.com/argoproj-labs/argocd-image-updater/pkg/metrics"

argocdclient "github.com/argoproj/argo-cd/pkg/apiclient"
"github.com/argoproj/argo-cd/pkg/apiclient/application"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// Kubernetes based client
type k8sClient struct {
kubeClient *kube.KubernetesClient
}

func (client *k8sClient) GetApplication(ctx context.Context, appName string) (*v1alpha1.Application, error) {
return client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).Get(ctx, appName, v1.GetOptions{})
}

func (client *k8sClient) ListApplications() ([]v1alpha1.Application, error) {
list, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).List(context.TODO(), v1.ListOptions{})
if err != nil {
return nil, err
}
return list.Items, nil
}

func (client *k8sClient) UpdateSpec(ctx context.Context, spec *application.ApplicationUpdateSpecRequest) (*v1alpha1.ApplicationSpec, error) {
for {
app, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).Get(ctx, spec.GetName(), v1.GetOptions{})
if err != nil {
return nil, err
}
app.Spec = spec.Spec

updatedApp, err := client.kubeClient.ApplicationsClientset.ArgoprojV1alpha1().Applications(client.kubeClient.Namespace).Update(ctx, app, v1.UpdateOptions{})
if err != nil {
if errors.IsConflict(err) {
continue
}
return nil, err
}
return &updatedApp.Spec, nil
}

}

// NewAPIClient creates a new API client for ArgoCD and connects to the ArgoCD
// API server.
func NewK8SClient(kubeClient *kube.KubernetesClient) (ArgoCD, error) {
return &k8sClient{kubeClient: kubeClient}, nil
}

// Native
type argoCD struct {
Client argocdclient.Client
Expand Down Expand Up @@ -49,9 +95,9 @@ type ClientOptions struct {
AuthToken string
}

// NewClient creates a new API client for ArgoCD and connects to the ArgoCD
// NewAPIClient creates a new API client for ArgoCD and connects to the ArgoCD
// API server.
func NewClient(opts *ClientOptions) (ArgoCD, error) {
func NewAPIClient(opts *ClientOptions) (ArgoCD, error) {

envAuthToken := os.Getenv("ARGOCD_TOKEN")
if envAuthToken != "" && opts.AuthToken == "" {
Expand Down
Loading

0 comments on commit 707891a

Please sign in to comment.