Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow image updates to be forced if container not present #181 #189

Merged
merged 2 commits into from
Apr 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/configuration/images.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ of the [Semver library](https://github.com/Masterminds/semver) we're using.
[filtering tags](#filtering-tags)
below.


### Forcing Image Updates
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for documentation update :)


By default, Image Updater will only update an image that is actually used in your Application
(i.e., is it exported in the Status of your ArgoCD Application.)

To support custom resources and things like PodTemplates that don't actually create a container,
you may force an update:

```yaml
argocd-image-updater.argoproj.io/image-list: myalias=some/image
argocd-image-updater.argoproj.io/myalias.force-update: "true"
```


## Assigning aliases to images

It's possible (and sometimes necessary) to assign an alias name to any given
Expand Down
30 changes: 24 additions & 6 deletions pkg/argocd/argocd.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,21 +172,26 @@ func FilterApplicationsForUpdate(apps []v1alpha1.Application, patterns []string)
continue
} else {
log.Tracef("processing app '%s' of type '%v'", app.GetName(), app.Status.SourceType)
imageList := make(image.ContainerImageList, 0)
for _, imageName := range strings.Split(updateImage, ",") {
allowed := image.NewFromIdentifier(strings.TrimSpace(imageName))
imageList = append(imageList, allowed)
}
imageList := parseImageList(updateImage)
appImages := ApplicationImages{}
appImages.Application = app
appImages.Images = imageList
appImages.Images = *imageList
appsForUpdate[app.GetName()] = appImages
}
}

return appsForUpdate, nil
}

func parseImageList(updateImage string) *image.ContainerImageList {
splits := strings.Split(updateImage, ",")
results := make(image.ContainerImageList, len(splits))
for i, s := range splits {
results[i] = image.NewFromIdentifier(strings.TrimSpace(s))
}
return &results
}

// GetApplication gets the application named appName from Argo CD API
func (client *argoCD) GetApplication(ctx context.Context, appName string) (*v1alpha1.Application, error) {
conn, appClient, err := client.Client.NewApplicationClient()
Expand Down Expand Up @@ -417,6 +422,19 @@ func GetImagesFromApplication(app *v1alpha1.Application) image.ContainerImageLis
images = append(images, image)
}

// The Application may wish to update images that don't create a container we can detect.
// Check the image list for images with a force-update annotation, and add them if they are not already present.
annotations := app.Annotations
if updateImage, ok := annotations[common.ImageUpdaterAnnotation]; ok {
iamnoah marked this conversation as resolved.
Show resolved Hide resolved
for _, img := range *parseImageList(updateImage) {
if forceStr, force := annotations[fmt.Sprintf(common.ForceUpdateOptionAnnotation, img.ImageAlias)]; force && strings.ToLower(forceStr) == "true" {
if images.ContainsImage(img, false) == nil {
images = append(images, img)
}
}
}
}

return images
}

Expand Down
27 changes: 27 additions & 0 deletions pkg/argocd/argocd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@ func Test_GetImagesFromApplication(t *testing.T) {
imageList := GetImagesFromApplication(application)
assert.Empty(t, imageList)
})

t.Run("Get list of images from application that has force-update", func(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests are always appreciated, thank you! 👍

application := &v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{
Name: "test-app",
Namespace: "argocd",
Annotations: map[string]string{
fmt.Sprintf(common.ForceUpdateOptionAnnotation, "nginx"): "true",
common.ImageUpdaterAnnotation: "nginx=nginx",
},
},
Spec: v1alpha1.ApplicationSpec{},
Status: v1alpha1.ApplicationStatus{
Summary: v1alpha1.ApplicationSummary{},
},
}
imageList := GetImagesFromApplication(application)
require.Len(t, imageList, 1)
assert.Equal(t, "nginx", imageList[0].ImageName)
})
}

func Test_GetApplicationType(t *testing.T) {
Expand Down Expand Up @@ -702,3 +722,10 @@ func TestKubernetesClient_UpdateSpec_Conflict(t *testing.T) {

assert.Equal(t, "https://github.com/argoproj/argocd-example-apps", spec.Source.RepoURL)
}

func Test_parseImageList(t *testing.T) {
assert.Equal(t, []string{"foo", "bar"}, parseImageList(" foo, bar ").Originals())
// should whitespace inside the spec be preserved?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think whitespace stripping should be done here, but let's do that in another PR:

func getImageTagFromIdentifier(identifier string) (string, string, *tag.ImageTag) {

assert.Equal(t, []string{"foo", "bar", "baz = qux"}, parseImageList(" foo, bar,baz = qux ").Originals())
assert.Equal(t, []string{"foo", "bar", "baz=qux"}, parseImageList("foo,bar,baz=qux").Originals())
}
9 changes: 5 additions & 4 deletions pkg/common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ const (

// Upgrade strategy related annotations
const (
OldMatchOptionAnnotation = ImageUpdaterAnnotationPrefix + "/%s.tag-match" // Deprecated and will be removed
AllowTagsOptionAnnotation = ImageUpdaterAnnotationPrefix + "/%s.allow-tags"
IgnoreTagsOptionAnnotation = ImageUpdaterAnnotationPrefix + "/%s.ignore-tags"
UpdateStrategyAnnotation = ImageUpdaterAnnotationPrefix + "/%s.update-strategy"
OldMatchOptionAnnotation = ImageUpdaterAnnotationPrefix + "/%s.tag-match" // Deprecated and will be removed
AllowTagsOptionAnnotation = ImageUpdaterAnnotationPrefix + "/%s.allow-tags"
IgnoreTagsOptionAnnotation = ImageUpdaterAnnotationPrefix + "/%s.ignore-tags"
ForceUpdateOptionAnnotation = ImageUpdaterAnnotationPrefix + "/%s.force-update"
UpdateStrategyAnnotation = ImageUpdaterAnnotationPrefix + "/%s.update-strategy"
)

// Image pull secret related annotations
Expand Down
8 changes: 8 additions & 0 deletions pkg/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ func (list *ContainerImageList) ContainsImage(img *ContainerImage, checkVersion
return nil
}

func (list *ContainerImageList) Originals() []string {
results := make([]string, len(*list))
for i, img := range *list {
results[i] = img.Original()
}
return results
}

// String Returns the name of all images as a string, separated using comma
func (list *ContainerImageList) String() string {
imgNameList := make([]string, 0)
Expand Down