Skip to content

feat: add Image Updater to GitOPS console#244

Open
dkarpele wants to merge 1 commit into
redhat-developer:mainfrom
dkarpele:dk-GITOPS-9213
Open

feat: add Image Updater to GitOPS console#244
dkarpele wants to merge 1 commit into
redhat-developer:mainfrom
dkarpele:dk-GITOPS-9213

Conversation

@dkarpele
Copy link
Copy Markdown

@dkarpele dkarpele commented May 26, 2026

I tested this against Image Updater v1.2.1 that is a part of GitOPS 1.21. status field below is available starting from v1.2.0.

% oc get imageupdater/image-updater-004 -n openshift-gitops -o yaml 
apiVersion: argocd-image-updater.argoproj.io/v1alpha1
kind: ImageUpdater
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"argocd-image-updater.argoproj.io/v1alpha1","kind":"ImageUpdater","metadata":{"annotations":{},"labels":{"app.kubernetes.io/managed-by":"kustomize","app.kubernetes.io/name":"image-updater"},"name":"image-updater-004","namespace":"openshift-gitops"},"spec":{"applicationRefs":[{"commonUpdateSettings":{"updateStrategy":"semver"},"images":[{"alias":"test-nginx","imageName":"nginx:1.17.10"},{"alias":"test-memcached","imageName":"memcached:1.6.10"}],"namePattern":"app-2"}]}}
  creationTimestamp: "2026-05-26T16:43:54Z"
  finalizers:
  - resources-finalizer.argocd-image-updater.argoproj.io
  generation: 1
  labels:
    app.kubernetes.io/managed-by: kustomize
    app.kubernetes.io/name: image-updater
  name: image-updater-004
  namespace: openshift-gitops
  resourceVersion: "212194"
  uid: 329b0f84-2084-43dd-8638-17c40958ef22
spec:
  applicationRefs:
  - commonUpdateSettings:
      forceUpdate: false
      updateStrategy: semver
    images:
    - alias: test-nginx
      imageName: nginx:1.17.10
    - alias: test-memcached
      imageName: memcached:1.6.10
    namePattern: app-2
    useAnnotations: false
status:
  applicationsMatched: 1
  conditions:
  - lastTransitionTime: "2026-05-26T16:43:55Z"
    message: Last check completed. Awaiting next cycle.
    observedGeneration: 1
    reason: Idle
    status: "False"
    type: Reconciling
  - lastTransitionTime: "2026-05-26T16:43:55Z"
    message: No errors during last reconciliation.
    observedGeneration: 1
    reason: NoErrors
    status: "False"
    type: Error
  - lastTransitionTime: "2026-05-26T16:43:55Z"
    message: Reconciled 1 applications, 2 images updated.
    observedGeneration: 1
    reason: ReconcileSucceeded
    status: "True"
    type: Ready
  imagesManaged: 2
  lastCheckedAt: "2026-05-26T16:43:55Z"
  lastUpdatedAt: "2026-05-26T16:43:55Z"
  observedGeneration: 1
  recentUpdates:
  - alias: test-nginx
    applicationsUpdated: 1
    image: nginx
    message: Updated from 1.17.0 to 1.17.10.
    newVersion: 1.17.10
    updatedAt: "2026-05-26T16:43:55Z"
  - alias: test-memcached
    applicationsUpdated: 1
    image: memcached
    message: Updated from 1.6.0 to 1.6.10.
    newVersion: 1.6.10
    updatedAt: "2026-05-26T16:43:55Z"
image image image image image image image image image image

Signed-off-by: dkarpele <karpelevich@gmail.com>
@openshift-ci openshift-ci Bot requested review from keithchong and wtam2018 May 26, 2026 16:35
@ranakan19
Copy link
Copy Markdown
Collaborator

@coderabbitai do you have any thoughts? inital review of the changes?

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get PR details and file list
gh pr view 244 --repo redhat-developer/gitops-console-plugin --json title,body,files,additions,deletions,changedFiles 2>/dev/null | jq '{title, additions, deletions, changedFiles, files: [.files[].path]}'

Length of output: 1356


🏁 Script executed:

#!/bin/bash
# Get the diff for the PR
gh pr diff 244 --repo redhat-developer/gitops-console-plugin 2>/dev/null | head -2000

Length of output: 67810



PR description images show:

  1. ImageUpdaters list view — table with Name, Namespace, Apps, Images, Last Checked, Ready columns and a "Create ImageUpdater" button.
  2. Create form — YAML editor with live schema docs panel.
  3. Filter dropdown — "Has Apps / No Apps" and "Ready / Not Ready" row filters.
  4. Detail page — name, namespace, labels, annotations, conditions table.
  5. Recent Updates tab — alias, image, new version, apps updated, updated at, message.
  6. YAML tab — full resource YAML with edit/save/download.
  7. Kebab menu — Edit labels, Edit annotations, Edit ImageUpdater, Delete ImageUpdater.
  8. Empty states — "No ImageUpdaters" and "No recent updates".

@ranakan19 — overall this is a solid, well-structured feature addition that follows the existing patterns for AppProjects and Rollouts nicely. The UI screenshots look polished. Here are my thoughts broken into severity levels:


🔴 Bugs

1. Multi-select filter short-circuits incorrectly (ImageUpdaterList.tsx)

When a user selects both "Has Apps" and "No Apps" (or both "Ready" and "Not Ready"), the filter should return true for all items, but currently it returns only the result of the first matching includes branch:

// Current — if both 'has-apps' and 'no-apps' are in input.selected,
// only the first if-branch fires (returns hasApps), never checking the second
filter: (input, item) => {
  if (input.selected?.length) {
    const hasApps = apps > 0;
    if (input.selected.includes('has-apps')) { return hasApps; }   // ← short-circuits
    if (input.selected.includes('no-apps'))  { return !hasApps; }
  }
  return true;
},

Suggested fix:

filter: (input, item) => {
  if (!input.selected?.length) return true;
  const apps = (item as ImageUpdaterKind).status?.applicationsMatched;
  const hasApps = apps > 0;
  return (
    (input.selected.includes('has-apps') && hasApps) ||
    (input.selected.includes('no-apps') && !hasApps)
  );
},

Same pattern applies to the ready-status filter.


2. Direct prop mutation (ImageUpdaterList.tsx, line ~83)

// ❌ mutates the destructured parameter
if (listAllNamespaces) {
  namespace = null;
}

Use a local variable instead:

const effectiveNamespace = listAllNamespaces ? null : namespace;
// then use effectiveNamespace everywhere below

🟠 Medium Issues

3. Missing flags guard on navigation entry (console-extensions.json)

The console.navigation/resource-ns for imageupdater has no flags guard, so the nav entry will always appear in the sidebar even when the ImageUpdater CRD is not installed:

// ❌ missing flags
{
  "type": "console.navigation/resource-ns",
  "properties": {
    "id": "imageupdater",
    ...
  }
}
// ✅ add flags guard
{
  "type": "console.navigation/resource-ns",
  "properties": { "id": "imageupdater", ... },
  "flags": { "required": ["IMAGEUPDATER"] }
}

The separator below it should also carry the same guard.


4. Hardcoded GVK literals instead of modelToGroupVersionKind (ImageUpdaterList.tsx, ImageUpdaterNavPage.tsx)

// ❌ hardcoded in two places
groupVersionKind: {
  group: 'argocd-image-updater.argoproj.io',
  kind: 'ImageUpdater',
  version: 'v1alpha1',
},
// ✅ use the model
groupVersionKind: modelToGroupVersionKind(ImageUpdaterModel),

5. Import style inconsistency (ImageUpdaterNavPage.tsx)

The file mixes @gitops alias imports with relative imports — all other imports in the same file use ../../:

// ❌ inconsistent with the rest of the file
import ImageUpdaterDetailsTab from "`@gitops/components/imageupdater/ImageUpdaterDetailsTab`";
import ImageUpdaterRecentUpdatesTab from "`@gitops/components/imageupdater/ImageUpdaterRecentUpdatesTab`";

Should be:

import ImageUpdaterDetailsTab from './ImageUpdaterDetailsTab';
import ImageUpdaterRecentUpdatesTab from './ImageUpdaterRecentUpdatesTab';

6. abbr inconsistency between model and console-extensions.json

ImageUpdaterModel.ts has abbr: 'imageupdater' while console-extensions.json has "abbr": "IU". The model abbreviation is also used as a CSS class for the resource badge icon, so a long string like 'imageupdater' will not display correctly in the badge circle. It should be 'IU' to match.


7. Loose equality checks

// ❌ use === instead of ==
if (imageUpdaterList == undefined || imageUpdaterList.length == 0)
if (showTitle == undefined)

🟡 Minor / Style

8. IIFE in JSX (ImageUpdaterDetailsTab.tsx)

The {(() => { ... })()} pattern is harder to read. A simple extracted variable or ternary would be cleaner:

// ❌
{(() => {
  const readyCondition = status.conditions?.find((c) => c.type === 'Ready');
  if (!readyCondition) return '-';
  return readyCondition.status === 'True' ? t('True') : t('False');
})()}

// ✅
{status.conditions?.find((c) => c.type === 'Ready')
  ? status.conditions.find((c) => c.type === 'Ready')!.status === 'True' ? t('True') : t('False')
  : '-'}
// or extract to a variable above the return

9. if (!obj) return null guard placement (ImageUpdaterRecentUpdatesTab.tsx)

The guard is currently placed near the bottom of the component body, after all hooks (useMemo, useGitOpsDataViewSort, etc.) have already run. While this technically complies with Rules of Hooks (hooks must not be conditional), it's confusing. Consider either placing the guard before non-hook logic or restructuring so all data derivation happens inside the render path.

10. PF class names

imageupdater-list.scss uses pf-c-table and ImageUpdaterDetailsTab.tsx uses pf-c-description-list — these are PatternFly v4 prefixes. If the project has migrated to PF v5, these should be pf-v5-c-table / pf-v5-c-description-list. Worth checking for consistency with other SCSS files in the project.

11. spec type missing from ImageUpdaterKind

ImageUpdaterKind only defines status. Even a minimal spec?: { applicationRefs?: unknown[] } would aid future work and improve type safety.


✅ What's done well

  • Clean separation of concerns across files (NavPage, DetailsTab, RecentUpdatesTab, List, ListTab, ActionsProvider).
  • Consistent use of useK8sWatchResource + HorizontalNav pattern as seen in AppProject/ApplicationSet.
  • All string literals are i18n-wrapped and locale files are updated.
  • Empty states, error states, and loading states are all handled properly.
  • The status.conditions section is conditionally rendered when conditions are present — good defensive coding.
  • useImageUpdaterActionsProvider with accessReview on each action is correct.
  • The console.yaml-template registration for the Create form is a nice touch (screenshot 2).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants