Skip to content

Commit

Permalink
Merge pull request #49 from weaveworks/image-policies
Browse files Browse the repository at this point in the history
Add suport for Image policies
  • Loading branch information
AlinaGoaga authored Sep 15, 2023
2 parents 5663471 + 5f11b46 commit 8074b6d
Show file tree
Hide file tree
Showing 16 changed files with 631 additions and 23 deletions.
4 changes: 4 additions & 0 deletions packages/app/src/components/catalog/EntityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import {
EntityFluxHelmRepositoriesCard,
EntityFluxDeploymentsCard,
EntityFluxSourcesCard,
EntityFluxImagePoliciesCard,
} from '@weaveworksoss/backstage-plugin-flux';

const techdocsContent = (
Expand Down Expand Up @@ -176,6 +177,9 @@ const serviceEntityPage = (
<Grid item md={12}>
<EntityFluxSourcesCard />
</Grid>
<Grid item md={12}>
<EntityFluxImagePoliciesCard />
</Grid>
</Grid>
</EntityLayout.Route>

Expand Down
1 change: 1 addition & 0 deletions plugins/backstage-plugin-flux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ You can also add cards for resources with the following components, each of thes
- EntityFluxGitRepositoriesCard
- EntityFluxOCIRepositoriesCard
- EntityFluxHelmRepositoriesCard
- EntityFluxImagePoliciesCard

As with other Backstage plugins, you can compose the UI you need.

Expand Down
49 changes: 49 additions & 0 deletions plugins/backstage-plugin-flux/dev/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,52 @@ export const newTestHelmRelease = (
},
};
};

export const newTestImagePolicy = (
name: string,
policy: { [name: string]: { [name: string]: string } },
imageRepositoryRef: string,
latestImage: string,
ready: string = 'True',
) => {
return {
apiVersion: 'image.toolkit.fluxcd.io/v1beta1',
kind: 'ImagePolicy',
metadata: {
creationTimestamp: '2023-06-29T08:06:59Z',
finalizers: ['finalizers.fluxcd.io'],
generation: 2,
labels: {
'kustomize.toolkit.fluxcd.io/name': 'flux-system',
'kustomize.toolkit.fluxcd.io/namespace': 'flux-system',
},
name,
namespace: 'flux-system',
resourceVersion: '13621',
uid: '5009e51d-0fee-4f8e-9df1-7684c8aac4bd',
},
spec: {
imageRepositoryRef: {
name: imageRepositoryRef,
},
policy,
},
status: {
conditions: [
{
lastTransitionTime: DateTime.now()
.minus({ hours: randomInt(22) + 1 })
.toISO(),
message:
'Applied revision: main@sha1:c933408394a3af8fa7208af8c9abf7fe430f99d4',
observedGeneration: 1,
reason: 'ReconciliationSucceeded',
status: ready,
type: 'Ready',
},
],
latestImage,
observedGeneration: 2,
},
};
};
42 changes: 42 additions & 0 deletions plugins/backstage-plugin-flux/dev/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ import {
EntityFluxHelmRepositoriesCard,
EntityFluxKustomizationsCard,
EntityFluxDeploymentsCard,
EntityFluxImagePoliciesCard,
} from '../src/plugin';
import {
newTestHelmRelease,
newTestOCIRepository,
newTestGitRepository,
newTestKustomization,
newTestHelmRepository,
newTestImagePolicy,
} from './helpers';
import { ReconcileRequestAnnotation } from '../src/hooks';
import { EntityFluxSourcesCard } from '../src/components/EntityFluxSourcesCard';
Expand Down Expand Up @@ -563,5 +565,45 @@ createDevApp()
</TestApiProvider>
),
})
.addPage({
title: 'ImagePolicies',
path: '/imagepolicies',
element: (
<TestApiProvider
apis={[
[
configApiRef,
new ConfigReader({
gitops: { baseUrl: 'https://example.com/wego' },
}),
],
[
kubernetesApiRef,
new StubKubernetesClient([
newTestImagePolicy(
'podinfo',
{ semver: { range: '5.0.x' } },
'podinfo',
'ghcr.io/stefanprodan/podinfo:5.0.3',
),
newTestImagePolicy(
'test',
{ numerical: { order: 'asc' } },
'test',
'ghcr.io/user/test:1.0.0',
),
]),
],
[kubernetesAuthProvidersApiRef, new StubKubernetesAuthProvidersApi()],
]}
>
<EntityProvider entity={fakeEntity}>
<Content>
<EntityFluxImagePoliciesCard />
</Content>
</EntityProvider>
</TestApiProvider>
),
})
.registerPlugin(weaveworksFluxPlugin)
.render();
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import React from 'react';
import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { configApiRef } from '@backstage/core-plugin-api';
import { ConfigReader } from '@backstage/core-app-api';
import {
KubernetesApi,
kubernetesApiRef,
KubernetesAuthProvidersApi,
kubernetesAuthProvidersApiRef,
} from '@backstage/plugin-kubernetes';
import {
CustomObjectsByEntityRequest,
KubernetesRequestBody,
ObjectsByEntityResponse,
} from '@backstage/plugin-kubernetes-common';
import { EntityFluxImagePoliciesCard } from './EntityFluxImagePoliciesCard';

const makeTestImagePolicy = (
name: string,
policy: { [name: string]: { [name: string]: string } },
imageRepositoryRef: string,
latestImage?: string,
) => {
return {
apiVersion: 'image.toolkit.fluxcd.io/v1beta1',
kind: 'ImagePolicy',
metadata: {
creationTimestamp: '2023-06-29T08:06:59Z',
finalizers: ['finalizers.fluxcd.io'],
generation: 2,
labels: {
'kustomize.toolkit.fluxcd.io/name': 'flux-system',
'kustomize.toolkit.fluxcd.io/namespace': 'flux-system',
},
name,
namespace: 'flux-system',
resourceVersion: '13621',
uid: '5009e51d-0fee-4f8e-9df1-7684c8aac4bd',
},
spec: {
imageRepositoryRef: {
name: imageRepositoryRef,
},
policy,
},
status: {
conditions: [
{
lastTransitionTime: '2023-07-03T16:18:04Z',
message:
'Applied revision: main@sha1:c933408394a3af8fa7208af8c9abf7fe430f99d4',
observedGeneration: 1,
reason: 'ReconciliationSucceeded',
status: 'True',
type: 'Ready',
},
],
latestImage,
observedGeneration: 2,
},
};
};

class StubKubernetesClient implements KubernetesApi {
getObjectsByEntity = jest.fn();

async getClusters(): Promise<{ name: string; authProvider: string }[]> {
return [{ name: 'mock-cluster', authProvider: 'serviceAccount' }];
}

getWorkloadsByEntity = jest.fn();

getCustomObjectsByEntity(
_: CustomObjectsByEntityRequest,
): Promise<ObjectsByEntityResponse> {
return Promise.resolve({
items: [
{
cluster: {
name: 'demo-cluster',
},
podMetrics: [],
errors: [],
resources: [
{
type: 'customresources',
resources: [
makeTestImagePolicy(
'podinfo',
{ semver: { range: '5.0.x' } },
'podinfo',
'ghcr.io/stefanprodan/podinfo:5.0.3',
),
makeTestImagePolicy(
'test',
{ numerical: { order: 'asc' } },
'test',
'ghcr.io/user/test:1.0.0',
),
],
},
],
},
],
});
}

proxy = jest.fn();
}

class StubKubernetesAuthProvidersApi implements KubernetesAuthProvidersApi {
decorateRequestBodyForAuth(
_: string,
requestBody: KubernetesRequestBody,
): Promise<KubernetesRequestBody> {
return Promise.resolve(requestBody);
}
getCredentials(_: string): Promise<{
token?: string;
}> {
return Promise.resolve({ token: 'mock-token' });
}
}

const entity: Entity = {
apiVersion: 'v1',
kind: 'Component',
metadata: {
name: 'my-name',
annotations: {
'backstage.io/kubernetes-id': 'testing-service',
},
},
};

describe('<FluxImagePoliciesCard />', () => {
let Wrapper: React.ComponentType<React.PropsWithChildren<{}>>;

beforeEach(() => {
Wrapper = ({ children }: { children?: React.ReactNode }) => (
<div>{children}</div>
);
});

beforeEach(() => {
jest.resetAllMocks();
});

describe('listing Image Policies', () => {
it('shows the details of an image policy', async () => {
const result = await renderInTestApp(
<Wrapper>
<TestApiProvider
apis={[
[
configApiRef,
new ConfigReader({
gitops: { baseUrl: 'https://example.com/wego' },
}),
],
[kubernetesApiRef, new StubKubernetesClient()],
[
kubernetesAuthProvidersApiRef,
new StubKubernetesAuthProvidersApi(),
],
]}
>
<EntityProvider entity={entity}>
<EntityFluxImagePoliciesCard />
</EntityProvider>
</TestApiProvider>
</Wrapper>,
);

const { getByText } = result;

const testCases = [
{
name: 'podinfo',
imagePolicy: 'semver / 5.0.x',
imageRepositoryRef: 'podinfo',
latestImage: 'ghcr.io/stefanprodan/podinfo:5.0.3',
},
{
name: 'test',
imagePolicy: 'numerical / asc',
imageRepositoryRef: 'test',
latestImage: 'ghcr.io/user/test:1.0.0',
},
];

for (const testCase of testCases) {
const cell = getByText(testCase.name);
expect(cell).toBeInTheDocument();

const tr = cell.closest('tr');
expect(tr).toBeInTheDocument();
expect(tr).toHaveTextContent(testCase.imagePolicy);
expect(tr).toHaveTextContent(testCase.imageRepositoryRef);
expect(tr).toHaveTextContent(testCase.latestImage);
}
});
});
});
Loading

0 comments on commit 8074b6d

Please sign in to comment.