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

[harbor] Add support for multi-arch images #224

Merged
merged 1 commit into from
Dec 6, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan
- [#219](https://github.com/kobsio/kobs/pull/219): [azure] Add permissions for Azure plugin, so that access to resources and actions can be restricted based on resource groups.
- [#220](https://github.com/kobsio/kobs/pull/220): [azure] Add auto formatting for the returned metrics of an container instance and fix the tooltip positioning in the metrics chart.
- [#221](https://github.com/kobsio/kobs/pull/221): [azure] :warning: _Breaking change:_ :warning: Add support for kubernetes services and refactor various places in the Azure plugin.
- [#224](https://github.com/kobsio/kobs/pull/224): [harbor] Add support for multi-arch images.

### Fixed

Expand Down
24 changes: 24 additions & 0 deletions plugins/harbor/harbor.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,29 @@ func (router *Router) getArtifacts(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, artifactsData)
}

func (router *Router) getArtifact(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
projectName := r.URL.Query().Get("projectName")
repositoryName := r.URL.Query().Get("repositoryName")
artifactReference := r.URL.Query().Get("artifactReference")

log.WithFields(logrus.Fields{"name": name, "projectName": projectName, "repositoryName": repositoryName, "artifactReference": artifactReference}).Tracef("getArtifact")

i := router.getInstance(name)
if i == nil {
errresponse.Render(w, r, nil, http.StatusBadRequest, "Could not find instance name")
return
}

artifact, err := i.GetArtifact(r.Context(), projectName, repositoryName, artifactReference)
if err != nil {
errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get artifact")
return
}

render.JSON(w, r, artifact)
}

func (router *Router) getVulnerabilities(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
projectName := r.URL.Query().Get("projectName")
Expand Down Expand Up @@ -193,6 +216,7 @@ func Register(clusters *clusters.Clusters, plugins *plugin.Plugins, config Confi
router.Get("/projects/{name}", router.getProjects)
router.Get("/repositories/{name}", router.getRepositories)
router.Get("/artifacts/{name}", router.getArtifacts)
router.Get("/artifact/{name}", router.getArtifact)
router.Get("/vulnerabilities/{name}", router.getVulnerabilities)
router.Get("/buildhistory/{name}", router.getBuildHistory)

Expand Down
19 changes: 19 additions & 0 deletions plugins/harbor/pkg/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func (i *Instance) doRequest(ctx context.Context, url string) ([]byte, int64, er
return nil, 0, err
}

req.Header.Set("X-Accept-Vulnerabilities", "application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0")

resp, err := i.client.Do(req)
if err != nil {
return nil, 0, err
Expand Down Expand Up @@ -148,6 +150,23 @@ func (i *Instance) GetArtifacts(ctx context.Context, projectName, repositoryName
}, nil
}

// GetArtifact returns a single artifact from the Harbor instance.
func (i *Instance) GetArtifact(ctx context.Context, projectName, repositoryName, artifactReference string) (*Artifact, error) {
repositoryName = url.PathEscape(repositoryName)

body, _, err := i.doRequest(ctx, fmt.Sprintf("projects/%s/repositories/%s/artifacts/%s", projectName, repositoryName, artifactReference))
if err != nil {
return nil, err
}

var artifact Artifact
if err := json.Unmarshal(body, &artifact); err != nil {
return nil, err
}

return &artifact, nil
}

// GetVulnerabilities returns a list of artifacts for a repository from the Harbor instance.
func (i *Instance) GetVulnerabilities(ctx context.Context, projectName, repositoryName, artifactReference string) (map[string]Vulnerability, error) {
repositoryName = url.PathEscape(repositoryName)
Expand Down
14 changes: 13 additions & 1 deletion plugins/harbor/pkg/instance/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,26 @@ type Artifact struct {
ProjectID int64 `json:"project_id"`
PullTime time.Time `json:"pull_time"`
PushTime time.Time `json:"push_time"`
References interface{} `json:"references"`
References []ArtifactReference `json:"references"`
RepositoryID int64 `json:"repository_id"`
ScanOverview map[string]ArtifactScanOverview `json:"scan_overview"`
Size int64 `json:"size"`
Tags []ArtifactTag `json:"tags"`
Type string `json:"type"`
}

type ArtifactReference struct {
ChildDigest string `json:"child_digest"`
ChildID int64 `json:"child_id"`
ParentID int64 `json:"parent_id"`
Platform struct {
OsFeatures []string `json:"OsFeatures"`
Architecture string `json:"architecture"`
Os string `json:"os"`
} `json:"platform"`
Urls []string `json:"urls"`
}

type ArtifactScanOverview struct {
CompletePercent int64 `json:"complete_percent"`
Duration int64 `json:"duration"`
Expand Down
166 changes: 62 additions & 104 deletions plugins/harbor/src/components/panel/details/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@ import {
DrawerHead,
DrawerPanelBody,
DrawerPanelContent,
Tab,
TabTitleText,
Tabs,
Tooltip,
} from '@patternfly/react-core';
import React, { useState } from 'react';
import { TableComposable, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import { CopyIcon } from '@patternfly/react-icons';
import React from 'react';

import BuildHistory from './BuildHistory';
import { IArtifact } from '../../../utils/interfaces';
import Overview from './Overview';
import Reference from './Reference';
import { Title } from '@kobsio/plugin-core';
import Vulnerabilities from './Vulnerabilities';
import { formatTime } from '../../../utils/helpers';
Expand All @@ -37,6 +42,10 @@ const Details: React.FunctionComponent<IDetailsProps> = ({
artifact,
close,
}: IDetailsProps) => {
const [activeTab, setActiveTab] = useState<string>(
artifact.references && artifact.references.length > 0 ? artifact.references[0].child_digest : '',
);

const copyPullCommand = (tag: string): void => {
if (navigator.clipboard) {
navigator.clipboard.writeText(
Expand Down Expand Up @@ -98,113 +107,62 @@ const Details: React.FunctionComponent<IDetailsProps> = ({
</div>
)}

{artifact.extra_attrs && (
{artifact.references && artifact.references.length > 0 ? (
<Tabs
activeKey={activeTab}
onSelect={(event, tabIndex): void => setActiveTab(tabIndex.toString())}
className="pf-u-mt-md"
isFilled={true}
mountOnEnter={true}
>
{artifact.references.map((reference) => (
<Tab
key={reference.child_digest}
eventKey={reference.child_digest}
title={
<TabTitleText>
{reference.child_digest.substring(0, 15)} ({reference.platform.os}/{reference.platform.architecture}
)
</TabTitleText>
}
>
<div style={{ maxWidth: '100%', padding: '24px 24px' }}>
<Reference
name={name}
projectName={projectName}
repositoryName={repositoryName}
artifactReference={reference.child_digest}
/>
</div>
</Tab>
))}
</Tabs>
) : (
<div>
<Card isCompact={true}>
<CardTitle>Overview</CardTitle>
<CardBody>
<TableComposable aria-label="Details" variant={TableVariant.compact} borders={false}>
<Tbody>
{artifact.extra_attrs.created && (
<Tr key="created">
<Td>Created</Td>
<Td>{formatTime(artifact.extra_attrs.created)}</Td>
</Tr>
)}
{artifact.extra_attrs.author && (
<Tr key="author">
<Td>Author</Td>
<Td>{artifact.extra_attrs.author}</Td>
</Tr>
)}
{artifact.extra_attrs.architecture && (
<Tr key="architecture">
<Td>Architecture</Td>
<Td>{artifact.extra_attrs.architecture}</Td>
</Tr>
)}
{artifact.extra_attrs.os && (
<Tr key="os">
<Td>OS</Td>
<Td>{artifact.extra_attrs.os}</Td>
</Tr>
)}
{artifact.extra_attrs.config.Cmd && (
<Tr key="cmd">
<Td>Cmd</Td>
<Td style={{ whiteSpace: 'pre-wrap' }}>{artifact.extra_attrs.config.Cmd.join('\n')}</Td>
</Tr>
)}
{artifact.extra_attrs.config.Entrypoint && (
<Tr key="entrypoint">
<Td>Entrypoint</Td>
<Td style={{ whiteSpace: 'pre-wrap' }}>{artifact.extra_attrs.config.Entrypoint.join('\n')}</Td>
</Tr>
)}
{artifact.extra_attrs.config.Env && (
<Tr key="env">
<Td>Env</Td>
<Td style={{ whiteSpace: 'pre-wrap' }}>{artifact.extra_attrs.config.Env.join('\n')}</Td>
</Tr>
)}
{artifact.extra_attrs.config.ExposedPorts && (
<Tr key="ports">
<Td>Ports</Td>
<Td style={{ whiteSpace: 'pre-wrap' }}>
{Object.keys(artifact.extra_attrs.config.ExposedPorts).join('\n')}
</Td>
</Tr>
)}
{artifact.extra_attrs.config.Labels && (
<Tr key="labels">
<Td>Labels</Td>
<Td style={{ whiteSpace: 'pre-wrap' }}>
{Object.keys(artifact.extra_attrs.config.Labels)
.map(
(key) =>
`${key}=${
artifact.extra_attrs.config.Labels && artifact.extra_attrs.config.Labels[key]
}`,
)
.join('\n')}
</Td>
</Tr>
)}
{artifact.extra_attrs.config.User && (
<Tr key="user">
<Td>User</Td>
<Td>{artifact.extra_attrs.config.User}</Td>
</Tr>
)}
{artifact.extra_attrs.config.WorkingDir && (
<Tr key="workingdir">
<Td>Workind Dir</Td>
<Td>{artifact.extra_attrs.config.WorkingDir}</Td>
</Tr>
)}
</Tbody>
</TableComposable>
</CardBody>
</Card>
{artifact.extra_attrs && (
<div>
<Overview artifact={artifact} />
<p>&nbsp;</p>
</div>
)}

<BuildHistory
name={name}
projectName={projectName}
repositoryName={repositoryName}
artifactReference={artifact.digest}
/>
<p>&nbsp;</p>

<Vulnerabilities
name={name}
projectName={projectName}
repositoryName={repositoryName}
artifactReference={artifact.digest}
/>
<p>&nbsp;</p>
</div>
)}

<BuildHistory
name={name}
projectName={projectName}
repositoryName={repositoryName}
artifactReference={artifact.digest}
/>
<p>&nbsp;</p>

<Vulnerabilities
name={name}
projectName={projectName}
repositoryName={repositoryName}
artifactReference={artifact.digest}
/>
<p>&nbsp;</p>
</DrawerPanelBody>
</DrawerPanelContent>
);
Expand Down
Loading