Compare commits
5 Commits
commit-6a2
...
commit-70a
Author | SHA1 | Date | |
---|---|---|---|
70a870a616 | |||
c09a12facc | |||
415973e23c | |||
cb2d8795f5 | |||
ac9d023272 |
2
.github/workflows/end-to-end-test.yml
vendored
2
.github/workflows/end-to-end-test.yml
vendored
@ -73,7 +73,7 @@ jobs:
|
||||
- name: Install go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19.x
|
||||
go-version: 1.20.x
|
||||
|
||||
- name: Checkout zot repo
|
||||
uses: actions/checkout@v3
|
||||
|
19789
package-lock.json
generated
19789
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -69,7 +69,7 @@ const mockedTagsData = [
|
||||
describe('Tags component', () => {
|
||||
it('should open and close details dropdown for tags', async () => {
|
||||
render(<TagsThemeWrapper />);
|
||||
const openBtn = screen.getAllByText(/digest/i);
|
||||
const openBtn = screen.getAllByText(/show/i);
|
||||
fireEvent.click(openBtn[0]);
|
||||
expect(screen.getByText(/OS\/ARCH/i)).toBeInTheDocument();
|
||||
fireEvent.click(openBtn[0]);
|
||||
@ -87,7 +87,7 @@ describe('Tags component', () => {
|
||||
|
||||
it('should navigate to specific manifest when clicking the digest', async () => {
|
||||
render(<TagsThemeWrapper />);
|
||||
const openBtn = screen.getAllByText(/digest/i);
|
||||
const openBtn = screen.getAllByText(/show/i);
|
||||
await fireEvent.click(openBtn[0]);
|
||||
const tagLink = await screen.findByText(/sha256:adca4/i);
|
||||
fireEvent.click(tagLink);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import MockThemeProvier from '__mocks__/MockThemeProvider';
|
||||
import { api } from 'api';
|
||||
import VulnerabilitiesDetails from 'components/Tag/Tabs/VulnerabilitiesDetails';
|
||||
import React from 'react';
|
||||
@ -7,9 +8,11 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
const StateVulnerabilitiesWrapper = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<VulnerabilitiesDetails name="mongo" />
|
||||
</MemoryRouter>
|
||||
<MockThemeProvier>
|
||||
<MemoryRouter>
|
||||
<VulnerabilitiesDetails name="mongo" />
|
||||
</MemoryRouter>
|
||||
</MockThemeProvier>
|
||||
);
|
||||
};
|
||||
|
||||
@ -500,7 +503,7 @@ describe('Vulnerabilties page', () => {
|
||||
it('sends filtered query if user types in the search bar', async () => {
|
||||
jest.spyOn(api, 'get').mockResolvedValue({ status: 200, data: { data: mockCVEList } });
|
||||
render(<StateVulnerabilitiesWrapper />);
|
||||
const cveSearchInput = screen.getByPlaceholderText(/search for/i);
|
||||
const cveSearchInput = screen.getByPlaceholderText(/search/i);
|
||||
jest.spyOn(api, 'get').mockRejectedValueOnce({ status: 200, data: { data: mockCVEListFiltered } });
|
||||
await userEvent.type(cveSearchInput, '2022');
|
||||
expect((await screen.queryAllByText(/2023/i).length) === 0);
|
||||
|
@ -77,6 +77,9 @@ const useStyles = makeStyles((theme) => ({
|
||||
width: '100%',
|
||||
boxShadow: 'none!important'
|
||||
},
|
||||
tagsContent: {
|
||||
padding: '1.5rem'
|
||||
},
|
||||
platformText: {
|
||||
backgroundColor: '#EDE7F6',
|
||||
color: '#220052',
|
||||
@ -290,7 +293,7 @@ function RepoDetails() {
|
||||
</Grid>
|
||||
<Grid item xs={12} md={8} className={classes.tags}>
|
||||
<Card className={classes.cardRoot}>
|
||||
<CardContent>
|
||||
<CardContent className={classes.tagsContent}>
|
||||
<Tags tags={tags} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
@ -3,33 +3,13 @@ import React, { useState } from 'react';
|
||||
|
||||
// components
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Card, CardContent, Stack, InputBase, FormControl, Select, InputLabel, MenuItem } from '@mui/material';
|
||||
import { Stack, InputBase, FormControl, Select, InputLabel, MenuItem } from '@mui/material';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import TagCard from '../../Shared/TagCard';
|
||||
import { tagsSortByCriteria } from 'utilities/sortCriteria';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
tagContainer: {
|
||||
marginBottom: 2,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
background: '#FFFFFF',
|
||||
boxShadow: 'none!important',
|
||||
borderRadius: '1.875rem',
|
||||
flex: 'none',
|
||||
alignSelf: 'stretch',
|
||||
flexGrow: 0,
|
||||
order: 0,
|
||||
width: '100%'
|
||||
},
|
||||
content: {
|
||||
textAlign: 'left',
|
||||
color: '#606060',
|
||||
padding: '2% 3% 2% 3%',
|
||||
width: '100%'
|
||||
},
|
||||
clickCursor: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
@ -39,8 +19,6 @@ const useStyles = makeStyles(() => ({
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: '1rem',
|
||||
marginBottom: '1rem',
|
||||
boxShadow: 'none',
|
||||
border: '0.063rem solid #E7E7E7',
|
||||
borderRadius: '0.625rem'
|
||||
@ -49,11 +27,14 @@ const useStyles = makeStyles(() => ({
|
||||
color: '#52637A',
|
||||
paddingRight: '3%'
|
||||
},
|
||||
searchInputBase: {
|
||||
width: '90%',
|
||||
paddingLeft: '1.5rem',
|
||||
height: 40
|
||||
},
|
||||
input: {
|
||||
color: '#464141',
|
||||
fontSize: '1rem',
|
||||
paddingLeft: '1rem',
|
||||
width: '90%',
|
||||
'&::placeholder': {
|
||||
opacity: '1'
|
||||
}
|
||||
@ -99,51 +80,45 @@ export default function Tags(props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={classes.tagContainer} data-testid="tags-container">
|
||||
<CardContent className={classes.content}>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
component="div"
|
||||
align="left"
|
||||
style={{ color: 'rgba(0, 0, 0, 0.87)', fontSize: '1.5rem', fontWeight: '600' }}
|
||||
<Stack direction="column" spacing="1rem">
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
component="div"
|
||||
align="left"
|
||||
style={{ color: 'rgba(0, 0, 0, 0.87)', fontSize: '1.5rem', fontWeight: '600' }}
|
||||
>
|
||||
Tags History
|
||||
</Typography>
|
||||
<FormControl sx={{ m: '1', minWidth: '4.6875rem' }} className={classes.sortForm} size="small">
|
||||
<InputLabel>Sort</InputLabel>
|
||||
<Select
|
||||
label="Sort"
|
||||
value={sortFilter}
|
||||
onChange={handleTagsSortChange}
|
||||
MenuProps={{ disableScrollLock: true }}
|
||||
>
|
||||
Tags History
|
||||
</Typography>
|
||||
<div>
|
||||
<FormControl sx={{ m: '1', minWidth: '4.6875rem' }} className={classes.sortForm} size="small">
|
||||
<InputLabel>Sort</InputLabel>
|
||||
<Select
|
||||
label="Sort"
|
||||
value={sortFilter}
|
||||
onChange={handleTagsSortChange}
|
||||
MenuProps={{ disableScrollLock: true }}
|
||||
>
|
||||
{Object.values(tagsSortByCriteria).map((el) => (
|
||||
<MenuItem key={el.value} value={el.value}>
|
||||
{el.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</Stack>
|
||||
<Stack className={classes.search}>
|
||||
<InputBase
|
||||
style={{ paddingLeft: 10, height: 40, color: 'rgba(0, 0, 0, 0.6)' }}
|
||||
placeholder={'Search tags...'}
|
||||
// className={classes.input}
|
||||
classes={{ input: classes.input }}
|
||||
value={tagsFilter}
|
||||
onChange={handleTagsFilterChange}
|
||||
/>
|
||||
<div className={classes.searchIcon}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
</Stack>
|
||||
{renderTags(tags)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
{Object.values(tagsSortByCriteria).map((el) => (
|
||||
<MenuItem key={el.value} value={el.value}>
|
||||
{el.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<Stack className={classes.search}>
|
||||
<InputBase
|
||||
placeholder={'Search tags...'}
|
||||
classes={{ root: classes.searchInputBase, input: classes.input }}
|
||||
value={tagsFilter}
|
||||
onChange={handleTagsFilterChange}
|
||||
/>
|
||||
<div className={classes.searchIcon}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
</Stack>
|
||||
{renderTags(tags)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
@ -32,6 +32,13 @@ const useStyles = makeStyles((theme) => ({
|
||||
clickCursor: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
dropdownToggle: {
|
||||
color: '#1479FF',
|
||||
paddingTop: '1rem',
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: '600',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
dropdown: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
@ -117,17 +124,7 @@ export default function TagCard(props) {
|
||||
) : (
|
||||
<KeyboardArrowDown className={classes.dropdownText} />
|
||||
)}
|
||||
<Typography
|
||||
sx={{
|
||||
color: '#1479FF',
|
||||
paddingTop: '1rem',
|
||||
fontSize: '0.8125rem',
|
||||
fontWeight: '600',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
DIGEST
|
||||
</Typography>
|
||||
<Typography className={classes.dropdownToggle}>{!open ? `Show more` : `Show less`}</Typography>
|
||||
</Stack>
|
||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||
<Box className={classes.manifsetsTable}>
|
||||
|
213
src/components/Shared/VulnerabilityCard.jsx
Normal file
213
src/components/Shared/VulnerabilityCard.jsx
Normal file
@ -0,0 +1,213 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
// utility
|
||||
import { api, endpoints } from '../../api';
|
||||
|
||||
// components
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import { Box, Card, CardContent, Stack, Typography, Divider } from '@mui/material';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { host } from '../../host';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material';
|
||||
import { VulnerabilityChipCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
||||
import { CVE_FIXEDIN_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
background: '#FFFFFF',
|
||||
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
|
||||
border: '1px solid #E0E5EB',
|
||||
borderRadius: '0.75rem',
|
||||
flex: 'none',
|
||||
alignSelf: 'stretch',
|
||||
width: '100%',
|
||||
marginTop: '2rem',
|
||||
marginBottom: '2rem'
|
||||
},
|
||||
content: {
|
||||
textAlign: 'left',
|
||||
color: '#606060',
|
||||
padding: '2% 3% 2% 3%',
|
||||
width: '100%'
|
||||
},
|
||||
cveId: {
|
||||
color: theme.palette.primary.main,
|
||||
fontSize: '1rem',
|
||||
fontWeight: 400,
|
||||
textDecoration: 'underline'
|
||||
},
|
||||
cveSummary: {
|
||||
color: theme.palette.secondary.dark,
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: '600',
|
||||
textOverflow: 'ellipsis',
|
||||
marginTop: '0.5rem'
|
||||
},
|
||||
link: {
|
||||
color: '#52637A',
|
||||
fontSize: '1rem',
|
||||
letterSpacing: '0.009375rem',
|
||||
paddingRight: '1rem',
|
||||
textDecorationLine: 'underline'
|
||||
},
|
||||
dropdown: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
dropdownText: {
|
||||
color: '#1479FF',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: '600',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'center'
|
||||
},
|
||||
vulnerabilityCardDivider: {
|
||||
margin: '1rem 0'
|
||||
}
|
||||
}));
|
||||
function VulnerabilitiyCard(props) {
|
||||
const classes = useStyles();
|
||||
const { cve, name } = props;
|
||||
const [openDesc, setOpenDesc] = useState(false);
|
||||
const [openFixed, setOpenFixed] = useState(false);
|
||||
const [loadingFixed, setLoadingFixed] = useState(true);
|
||||
const [fixedInfo, setFixedInfo] = useState([]);
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
|
||||
// pagination props
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const [isEndOfList, setIsEndOfList] = useState(false);
|
||||
|
||||
const getPaginatedResults = () => {
|
||||
if (!openFixed || isEndOfList) {
|
||||
return;
|
||||
}
|
||||
setLoadingFixed(true);
|
||||
api
|
||||
.get(
|
||||
`${host()}${endpoints.imageListWithCVEFixed(cve.id, name, { pageNumber, pageSize: CVE_FIXEDIN_PAGE_SIZE })}`,
|
||||
abortController.signal
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
const fixedTagsList = response.data.data.ImageListWithCVEFixed?.Results?.map((e) => e.Tag);
|
||||
setFixedInfo((previousState) => [...previousState, ...fixedTagsList]);
|
||||
setIsEndOfList(
|
||||
[...fixedInfo, ...fixedTagsList].length >= response.data.data.ImageListWithCVEFixed?.Page?.TotalCount
|
||||
);
|
||||
}
|
||||
setLoadingFixed(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setIsEndOfList(true);
|
||||
setLoadingFixed(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getPaginatedResults();
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [openFixed, pageNumber]);
|
||||
|
||||
const loadMore = () => {
|
||||
if (loadingFixed || isEndOfList) return;
|
||||
setPageNumber((pageNumber) => pageNumber + 1);
|
||||
};
|
||||
|
||||
const renderFixedVer = () => {
|
||||
if (!isEmpty(fixedInfo)) {
|
||||
return fixedInfo.map((tag, index) => {
|
||||
return (
|
||||
<Link key={index} to={`/image/${encodeURIComponent(name)}/tag/${tag}`} className={classes.link}>
|
||||
{tag}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return 'Not fixed';
|
||||
}
|
||||
};
|
||||
|
||||
const renderLoadMore = () => {
|
||||
return (
|
||||
!isEndOfList && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: '#3366CC',
|
||||
cursor: 'pointer',
|
||||
fontSize: '1rem',
|
||||
letterSpacing: '0.009375rem',
|
||||
paddingRight: '1rem',
|
||||
textDecorationLine: 'underline'
|
||||
}}
|
||||
onClick={loadMore}
|
||||
component="div"
|
||||
>
|
||||
Load more
|
||||
</Typography>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={classes.card} raised>
|
||||
<CardContent className={classes.content}>
|
||||
<Stack direction="row" spacing="1.25rem">
|
||||
<Typography variant="body1" align="left" className={classes.cveId}>
|
||||
{cve.id}
|
||||
</Typography>
|
||||
<VulnerabilityChipCheck vulnerabilitySeverity={cve.severity} />
|
||||
</Stack>
|
||||
<Typography variant="body1" align="left" className={classes.cveSummary}>
|
||||
{cve.title}
|
||||
</Typography>
|
||||
<Divider className={classes.vulnerabilityCardDivider} />
|
||||
<Stack className={classes.dropdown} onClick={() => setOpenFixed(!openFixed)}>
|
||||
{!openFixed ? (
|
||||
<KeyboardArrowRight className={classes.dropdownText} />
|
||||
) : (
|
||||
<KeyboardArrowDown className={classes.dropdownText} />
|
||||
)}
|
||||
<Typography className={classes.dropdownText}>Fixed in</Typography>
|
||||
</Stack>
|
||||
<Collapse in={openFixed} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ width: '100%', padding: '0.5rem 0' }}>
|
||||
{loadingFixed ? (
|
||||
'Loading...'
|
||||
) : (
|
||||
<Stack direction="row" sx={{ flexWrap: 'wrap' }}>
|
||||
{renderFixedVer()}
|
||||
{renderLoadMore()}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
</Collapse>
|
||||
<Stack className={classes.dropdown} onClick={() => setOpenDesc(!openDesc)}>
|
||||
{!openDesc ? (
|
||||
<KeyboardArrowRight className={classes.dropdownText} />
|
||||
) : (
|
||||
<KeyboardArrowDown className={classes.dropdownText} />
|
||||
)}
|
||||
<Typography className={classes.dropdownText}>Description</Typography>
|
||||
</Stack>
|
||||
<Collapse in={openDesc} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ padding: '0.5rem 0' }}>
|
||||
<Typography variant="body2" align="left" sx={{ color: '#0F2139', fontSize: '1rem' }}>
|
||||
{cve.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default VulnerabilitiyCard;
|
@ -14,52 +14,11 @@ import { mapToImage } from 'utilities/objectModels';
|
||||
import { EXPLORE_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
card: {
|
||||
background: '#FFFFFF',
|
||||
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
|
||||
borderRadius: '1.875rem',
|
||||
flex: 'none',
|
||||
alignSelf: 'stretch',
|
||||
flexGrow: 0,
|
||||
order: 0,
|
||||
width: '100%',
|
||||
marginTop: '2rem',
|
||||
marginBottom: '2rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start'
|
||||
},
|
||||
content: {
|
||||
textAlign: 'left',
|
||||
color: '#606060',
|
||||
padding: '2% 3% 2% 3%',
|
||||
width: '100%'
|
||||
},
|
||||
title: {
|
||||
color: '#828282',
|
||||
fontSize: '1rem',
|
||||
paddingRight: '0.5rem',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingTop: '0.5rem'
|
||||
},
|
||||
values: {
|
||||
color: '#000000',
|
||||
fontSize: '1rem',
|
||||
fontWeight: '600',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingTop: '0.5rem'
|
||||
},
|
||||
link: {
|
||||
color: '#52637A',
|
||||
fontSize: '1rem',
|
||||
letterSpacing: '0.009375rem',
|
||||
paddingRight: '1rem',
|
||||
textDecorationLine: 'underline'
|
||||
},
|
||||
monitor: {
|
||||
width: '27.25rem',
|
||||
height: '24.625rem',
|
||||
paddingTop: '2rem'
|
||||
marginBottom: '1.7rem',
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600'
|
||||
},
|
||||
none: {
|
||||
color: '#52637A',
|
||||
@ -172,18 +131,7 @@ function DependsOn(props) {
|
||||
|
||||
return (
|
||||
<div data-testid="depends-on-container">
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
component="div"
|
||||
align="left"
|
||||
style={{
|
||||
marginBottom: '1.7rem',
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4" gutterBottom component="div" align="left" className={classes.title}>
|
||||
Uses
|
||||
</Typography>
|
||||
<Stack direction="column" spacing={2}>
|
||||
|
@ -7,61 +7,11 @@ import makeStyles from '@mui/styles/makeStyles';
|
||||
import Loading from '../../Shared/Loading';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
background: '#FFFFFF',
|
||||
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
|
||||
borderRadius: '1.875rem',
|
||||
flex: 'none',
|
||||
alignSelf: 'stretch',
|
||||
flexGrow: 0,
|
||||
order: 0,
|
||||
width: '100%',
|
||||
marginTop: '0rem',
|
||||
marginBottom: '0rem',
|
||||
padding: '1rem 1.5rem '
|
||||
},
|
||||
content: {
|
||||
textAlign: 'left',
|
||||
color: '#606060',
|
||||
width: '100%',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
title: {
|
||||
color: '#14191F',
|
||||
fontSize: '1rem',
|
||||
fontWeight: '400',
|
||||
paddingRight: '0.5rem',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingTop: '0.5rem'
|
||||
},
|
||||
layer: {
|
||||
color: '#14191F',
|
||||
fontSize: '1rem',
|
||||
fontWeight: '400',
|
||||
paddingRight: '0.5rem',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingTop: '0.5rem',
|
||||
width: '100%',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
values: {
|
||||
color: '#52637A',
|
||||
fontSize: '1rem',
|
||||
fontWeight: '400',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingTop: '0.5rem',
|
||||
textAlign: 'right'
|
||||
},
|
||||
monitor: {
|
||||
width: '27.25rem',
|
||||
height: '24.625rem',
|
||||
paddingTop: '2rem'
|
||||
marginBottom: '1.7rem',
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600'
|
||||
},
|
||||
none: {
|
||||
color: '#52637A',
|
||||
@ -83,22 +33,11 @@ function HistoryLayers(props) {
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [name]);
|
||||
}, [name, history]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
component="div"
|
||||
align="left"
|
||||
style={{
|
||||
marginBottom: '1.7rem',
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4" gutterBottom component="div" align="left" className={classes.title}>
|
||||
Layers
|
||||
</Typography>
|
||||
{isLoading ? (
|
||||
|
@ -14,52 +14,11 @@ import { mapToImage } from 'utilities/objectModels';
|
||||
import { EXPLORE_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
card: {
|
||||
background: '#FFFFFF',
|
||||
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
|
||||
borderRadius: '1.875rem',
|
||||
flex: 'none',
|
||||
alignSelf: 'stretch',
|
||||
flexGrow: 0,
|
||||
order: 0,
|
||||
width: '100%',
|
||||
marginTop: '2rem',
|
||||
marginBottom: '2rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start'
|
||||
},
|
||||
content: {
|
||||
textAlign: 'left',
|
||||
color: '#606060',
|
||||
padding: '2% 3% 2% 3%',
|
||||
width: '100%'
|
||||
},
|
||||
title: {
|
||||
color: '#828282',
|
||||
fontSize: '1rem',
|
||||
paddingRight: '0.5rem',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingTop: '0.5rem'
|
||||
},
|
||||
values: {
|
||||
color: '#000000',
|
||||
fontSize: '1rem',
|
||||
fontWeight: '600',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingTop: '0.5rem'
|
||||
},
|
||||
link: {
|
||||
color: '#52637A',
|
||||
fontSize: '1rem',
|
||||
letterSpacing: '0.009375rem',
|
||||
paddingRight: '1rem',
|
||||
textDecorationLine: 'underline'
|
||||
},
|
||||
monitor: {
|
||||
width: '27.25rem',
|
||||
height: '24.625rem',
|
||||
paddingTop: '2rem'
|
||||
marginBottom: '1.7rem',
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600'
|
||||
},
|
||||
none: {
|
||||
color: '#52637A',
|
||||
@ -172,18 +131,7 @@ function IsDependentOn(props) {
|
||||
|
||||
return (
|
||||
<div data-testid="dependents-container">
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
component="div"
|
||||
align="left"
|
||||
style={{
|
||||
marginBottom: '1.7rem',
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4" gutterBottom component="div" align="left" className={classes.title}>
|
||||
Used by
|
||||
</Typography>
|
||||
<Stack direction="column" spacing={2}>
|
||||
|
@ -1,12 +1,18 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Divider, Typography, Stack } from '@mui/material';
|
||||
import { Typography, Stack } from '@mui/material';
|
||||
import ReferrerCard from '../../Shared/ReferrerCard';
|
||||
import Loading from '../../Shared/Loading';
|
||||
import { mapReferrer } from 'utilities/objectModels';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
title: {
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600',
|
||||
paddingTop: '0.5rem'
|
||||
},
|
||||
none: {
|
||||
color: '#52637A',
|
||||
fontSize: '1.4rem',
|
||||
@ -51,24 +57,9 @@ function ReferredBy(props) {
|
||||
|
||||
return (
|
||||
<div data-testid="referred-by-container">
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
component="div"
|
||||
align="left"
|
||||
style={{
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600',
|
||||
paddingTop: '0.5rem'
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4" gutterBottom component="div" align="left" className={classes.title}>
|
||||
Referred By
|
||||
</Typography>
|
||||
<Divider
|
||||
variant="fullWidth"
|
||||
sx={{ margin: '5% 0% 5% 0%', background: 'rgba(0, 0, 0, 0.38)', height: '0.00625rem', width: '100%' }}
|
||||
/>
|
||||
<Stack direction="column" spacing={2}>
|
||||
<Stack direction="column" spacing={2}>
|
||||
{isLoading ? <Loading /> : renderReferrers()}
|
||||
|
@ -4,250 +4,70 @@ import React, { useEffect, useMemo, useState, useRef } from 'react';
|
||||
import { api, endpoints } from '../../../api';
|
||||
|
||||
// components
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import { Box, Card, CardContent, Divider, Stack, Typography, InputBase } from '@mui/material';
|
||||
import { Stack, Typography, InputBase } from '@mui/material';
|
||||
import makeStyles from '@mui/styles/makeStyles';
|
||||
import { host } from '../../../host';
|
||||
import { debounce, isEmpty } from 'lodash';
|
||||
import { Link } from 'react-router-dom';
|
||||
import Loading from '../../Shared/Loading';
|
||||
import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material';
|
||||
import { VulnerabilityChipCheck } from 'utilities/vulnerabilityAndSignatureCheck';
|
||||
import { mapCVEInfo } from 'utilities/objectModels';
|
||||
import { CVE_FIXEDIN_PAGE_SIZE, EXPLORE_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||
import { EXPLORE_PAGE_SIZE } from 'utilities/paginationConstants';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
background: '#FFFFFF',
|
||||
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
|
||||
borderRadius: '1.875rem',
|
||||
flex: 'none',
|
||||
alignSelf: 'stretch',
|
||||
flexGrow: 0,
|
||||
order: 0,
|
||||
width: '100%',
|
||||
marginTop: '2rem',
|
||||
marginBottom: '2rem'
|
||||
},
|
||||
content: {
|
||||
textAlign: 'left',
|
||||
color: '#606060',
|
||||
padding: '2% 3% 2% 3%',
|
||||
width: '100%'
|
||||
},
|
||||
import VulnerabilitiyCard from '../../Shared/VulnerabilityCard';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
title: {
|
||||
color: '#828282',
|
||||
fontSize: '1rem',
|
||||
paddingRight: '0.5rem',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingTop: '0.5rem'
|
||||
},
|
||||
values: {
|
||||
color: '#000000',
|
||||
fontSize: '1rem',
|
||||
color: theme.palette.primary.main,
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600',
|
||||
paddingBottom: '0.5rem',
|
||||
paddingTop: '0.5rem',
|
||||
textOverflow: 'ellipsis'
|
||||
marginBottom: '0'
|
||||
},
|
||||
link: {
|
||||
color: '#52637A',
|
||||
cveId: {
|
||||
color: theme.palette.primary.main,
|
||||
fontSize: '1rem',
|
||||
letterSpacing: '0.009375rem',
|
||||
paddingRight: '1rem',
|
||||
textDecorationLine: 'underline'
|
||||
fontWeight: 400,
|
||||
textDecoration: 'underline'
|
||||
},
|
||||
monitor: {
|
||||
width: '27.25rem',
|
||||
height: '24.625rem',
|
||||
paddingTop: '2rem'
|
||||
cveSummary: {
|
||||
color: theme.palette.secondary.dark,
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: '600',
|
||||
textOverflow: 'ellipsis',
|
||||
marginTop: '0.5rem'
|
||||
},
|
||||
none: {
|
||||
color: '#52637A',
|
||||
fontSize: '1.4rem',
|
||||
fontWeight: '600'
|
||||
},
|
||||
dropdown: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
dropdownText: {
|
||||
color: '#1479FF',
|
||||
paddingTop: '1rem',
|
||||
fontSize: '1rem',
|
||||
fontWeight: '600',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'center'
|
||||
},
|
||||
search: {
|
||||
position: 'relative',
|
||||
minWidth: '100%',
|
||||
maxWidth: '100%',
|
||||
flexDirection: 'row',
|
||||
marginBottom: '1.7rem',
|
||||
boxShadow: '0rem 0.3125rem 0.625rem rgba(131, 131, 131, 0.08)',
|
||||
border: '0.125rem solid #E7E7E7',
|
||||
borderRadius: '1rem',
|
||||
zIndex: 1155
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
boxShadow: 'none',
|
||||
border: '0.063rem solid #E7E7E7',
|
||||
borderRadius: '0.625rem'
|
||||
},
|
||||
searchIcon: {
|
||||
color: '#52637A',
|
||||
paddingRight: '3%'
|
||||
},
|
||||
searchInputBase: {
|
||||
width: '90%',
|
||||
paddingLeft: '1.5rem',
|
||||
height: 40,
|
||||
color: 'rgba(0, 0, 0, 0.6)'
|
||||
},
|
||||
input: {
|
||||
color: '#464141',
|
||||
marginLeft: 1,
|
||||
width: '90%'
|
||||
'&::placeholder': {
|
||||
opacity: '1'
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
function VulnerabilitiyCard(props) {
|
||||
const classes = useStyles();
|
||||
const { cve, name } = props;
|
||||
const [openDesc, setOpenDesc] = useState(false);
|
||||
const [openFixed, setOpenFixed] = useState(false);
|
||||
const [loadingFixed, setLoadingFixed] = useState(true);
|
||||
const [fixedInfo, setFixedInfo] = useState([]);
|
||||
const abortController = useMemo(() => new AbortController(), []);
|
||||
|
||||
// pagination props
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const [isEndOfList, setIsEndOfList] = useState(false);
|
||||
|
||||
const getPaginatedResults = () => {
|
||||
if (!openFixed || isEndOfList) {
|
||||
return;
|
||||
}
|
||||
setLoadingFixed(true);
|
||||
api
|
||||
.get(
|
||||
`${host()}${endpoints.imageListWithCVEFixed(cve.id, name, { pageNumber, pageSize: CVE_FIXEDIN_PAGE_SIZE })}`,
|
||||
abortController.signal
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.data && response.data.data) {
|
||||
const fixedTagsList = response.data.data.ImageListWithCVEFixed?.Results?.map((e) => e.Tag);
|
||||
setFixedInfo((previousState) => [...previousState, ...fixedTagsList]);
|
||||
setIsEndOfList(
|
||||
[...fixedInfo, ...fixedTagsList].length >= response.data.data.ImageListWithCVEFixed?.Page?.TotalCount
|
||||
);
|
||||
}
|
||||
setLoadingFixed(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setIsEndOfList(true);
|
||||
setLoadingFixed(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getPaginatedResults();
|
||||
return () => {
|
||||
abortController.abort();
|
||||
};
|
||||
}, [openFixed, pageNumber]);
|
||||
|
||||
const loadMore = () => {
|
||||
if (loadingFixed || isEndOfList) return;
|
||||
setPageNumber((pageNumber) => pageNumber + 1);
|
||||
};
|
||||
|
||||
const renderFixedVer = () => {
|
||||
if (!isEmpty(fixedInfo)) {
|
||||
return fixedInfo.map((tag, index) => {
|
||||
return (
|
||||
<Link key={index} to={`/image/${encodeURIComponent(name)}/tag/${tag}`} className={classes.link}>
|
||||
{tag}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return 'Not fixed';
|
||||
}
|
||||
};
|
||||
|
||||
const renderLoadMore = () => {
|
||||
return (
|
||||
!isEndOfList && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: '#3366CC',
|
||||
cursor: 'pointer',
|
||||
fontSize: '1rem',
|
||||
letterSpacing: '0.009375rem',
|
||||
paddingRight: '1rem',
|
||||
textDecorationLine: 'underline'
|
||||
}}
|
||||
onClick={loadMore}
|
||||
component="div"
|
||||
>
|
||||
Load more
|
||||
</Typography>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={classes.card} raised>
|
||||
<CardContent className={classes.content}>
|
||||
<Stack sx={{ flexDirection: 'row' }}>
|
||||
<Typography variant="body1" align="left" className={classes.values}>
|
||||
{' '}
|
||||
{cve.id}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<VulnerabilityChipCheck vulnerabilitySeverity={cve.severity} />
|
||||
<Stack sx={{ flexDirection: 'row' }}>
|
||||
<Typography variant="body1" align="left" className={classes.values}>
|
||||
{' '}
|
||||
{cve.title}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack className={classes.dropdown} onClick={() => setOpenFixed(!openFixed)}>
|
||||
{!openFixed ? (
|
||||
<KeyboardArrowRight className={classes.dropdownText} />
|
||||
) : (
|
||||
<KeyboardArrowDown className={classes.dropdownText} />
|
||||
)}
|
||||
<Typography className={classes.dropdownText}>Fixed in</Typography>
|
||||
</Stack>
|
||||
<Collapse in={openFixed} timeout="auto" unmountOnExit>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
{loadingFixed ? (
|
||||
'Loading...'
|
||||
) : (
|
||||
<Stack direction="row" sx={{ flexWrap: 'wrap' }}>
|
||||
{renderFixedVer()}
|
||||
{renderLoadMore()}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
</Collapse>
|
||||
<Stack className={classes.dropdown} onClick={() => setOpenDesc(!openDesc)}>
|
||||
{!openDesc ? (
|
||||
<KeyboardArrowRight className={classes.dropdownText} />
|
||||
) : (
|
||||
<KeyboardArrowDown className={classes.dropdownText} />
|
||||
)}
|
||||
<Typography className={classes.dropdownText}>Description</Typography>
|
||||
</Stack>
|
||||
<Collapse in={openDesc} timeout="auto" unmountOnExit>
|
||||
<Box>
|
||||
<Typography variant="body2" align="left" sx={{ color: '#0F2139', fontSize: '1rem' }}>
|
||||
{' '}
|
||||
{cve.description}{' '}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Collapse>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function VulnerabilitiesDetails(props) {
|
||||
const classes = useStyles();
|
||||
const [cveData, setCveData] = useState([]);
|
||||
@ -369,48 +189,23 @@ function VulnerabilitiesDetails(props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div data-testid="vulnerability-container">
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
component="div"
|
||||
align="left"
|
||||
style={{
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: '600',
|
||||
paddingTop: '0.5rem'
|
||||
}}
|
||||
>
|
||||
<Stack direction="column" spacing="1rem" data-testid="vulnerability-container">
|
||||
<Typography variant="h4" gutterBottom component="div" align="left" className={classes.title}>
|
||||
Vulnerabilities
|
||||
</Typography>
|
||||
<Divider
|
||||
variant="fullWidth"
|
||||
sx={{
|
||||
margin: '5% 0% 5% 0%',
|
||||
background: 'rgba(0, 0, 0, 0.38)',
|
||||
height: '0.00625rem',
|
||||
width: '100%'
|
||||
}}
|
||||
/>
|
||||
<Stack className={classes.search} direction="row" alignItems="center" justifyContent="space-between" spacing={2}>
|
||||
<Stack className={classes.search}>
|
||||
<InputBase
|
||||
style={{ paddingLeft: 10, height: 46, color: 'rgba(0, 0, 0, 0.6)' }}
|
||||
placeholder={'Search for Vulnerabilities...'}
|
||||
className={classes.input}
|
||||
placeholder={'Search'}
|
||||
classes={{ root: classes.searchInputBase, input: classes.input }}
|
||||
onChange={debouncedChangeHandler}
|
||||
/>
|
||||
<div className={classes.searchIcon}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
</Stack>
|
||||
<Stack direction="column" spacing={2}>
|
||||
<Stack direction="column" spacing={2}>
|
||||
{renderCVEs()}
|
||||
{renderListBottom()}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
{renderCVEs()}
|
||||
{renderListBottom()}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -109,6 +109,9 @@ const useStyles = makeStyles((theme) => ({
|
||||
cardContent: {
|
||||
paddingBottom: '1rem'
|
||||
},
|
||||
tabCardContent: {
|
||||
padding: '1.5rem'
|
||||
},
|
||||
cardRoot: {
|
||||
boxShadow: 'none!important',
|
||||
borderRadius: '0.75rem'
|
||||
@ -314,7 +317,7 @@ function TagDetails() {
|
||||
</Grid>
|
||||
<Grid item xs={12} md={8}>
|
||||
<Card className={classes.cardRoot}>
|
||||
<CardContent className={classes.cardContent}>{renderTabContent()}</CardContent>
|
||||
<CardContent className={classes.tabCardContent}>{renderTabContent()}</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4} className={classes.metadata}>
|
||||
|
@ -7,7 +7,7 @@ cosign_password=""
|
||||
metafile=""
|
||||
multiarch=""
|
||||
username=""
|
||||
username=""
|
||||
password=""
|
||||
debug=0
|
||||
data_dir=$(pwd)
|
||||
|
||||
@ -110,28 +110,33 @@ cosign_key_path=${data_dir}/cosign.key
|
||||
function verify_prerequisites {
|
||||
mkdir -p ${data_dir}
|
||||
|
||||
if [ ! command -v regctl ] &>/dev/null; then
|
||||
echo "you need to install regctl as a prerequisite" >&3
|
||||
command -v regctl
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "you need to install regctl as a prerequisite"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command -v skopeo
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "you need to install skopeo as a prerequisite"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command -v cosign
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "you need to install cosign as a prerequisite"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! command -v skopeo ] &>/dev/null; then
|
||||
echo "you need to install skopeo as a prerequisite" >&3
|
||||
command -v trivy
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "you need to install trivy as a prerequisite"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! command -v cosign ] &>/dev/null; then
|
||||
echo "you need to install cosign as a prerequisite" >&3
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! command -v trivy ] &>/dev/null; then
|
||||
echo "you need to install trivy as a prerequisite" >&3
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! command -v jq ] &>/dev/null; then
|
||||
echo "you need to install jq as a prerequisite" >&3
|
||||
command -v jq
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "you need to install jq as a prerequisite"
|
||||
return 1
|
||||
fi
|
||||
|
||||
@ -197,7 +202,7 @@ regctl image mod --replace --annotation org.opencontainers.image.documentation="
|
||||
|
||||
credentials_args=""
|
||||
if [ ! -z "${username}" ]; then
|
||||
credentials_args="--dest-creds ${username}:${username}"
|
||||
credentials_args="--dest-creds ${username}:${password}"
|
||||
fi
|
||||
|
||||
# Upload image to target registry
|
||||
@ -224,8 +229,27 @@ else
|
||||
echo '{"trivy":[]}' > ${trivy_out_file}
|
||||
fi
|
||||
|
||||
layers_file=manifests-${image}-${tag}.json
|
||||
rm -f ${layers_file}
|
||||
|
||||
if [ -z "${multiarch}" ]; then
|
||||
regctl manifest --format raw-body get ${local_image_ref_regtl} | jq '{ manifests: { default: { layers: [ .layers[].digest ] } } }' > ${layers_file}
|
||||
else
|
||||
manifests=$(regctl manifest --format raw-body get ${local_image_ref_regtl} | jq '[ .manifests[] | { "digest":.digest, "platform":(.platform | [ .os, .architecture, .variant ] | map(select(.!=null)) | join("/") )} ] ')
|
||||
|
||||
echo $manifests | jq -c '.[]' | while read i; do
|
||||
digest=$(echo $i | jq -r '.digest')
|
||||
platform=$(echo $i | jq -r '.platform')
|
||||
regctl manifest --format raw-body get ocidir://${images_dir}@${digest} | jq --arg platform "${platform}" '{ manifests: { ($platform): { layers: [ .layers[].digest ] } } }' >> layers-${image}-${tag}-${digest//:/_}.json
|
||||
done
|
||||
|
||||
jq -n '{ manifests: [ inputs.manifests ] | add }' layers-${image}-${tag}*.json > ${layers_file}
|
||||
rm -f layers-${image}-${tag}*.json
|
||||
fi
|
||||
|
||||
|
||||
# Sign new updated image
|
||||
COSIGN_PASSWORD=${cosign_password} cosign sign ${remote_dest_image_ref} --key ${cosign_key_path} --allow-insecure-registry
|
||||
COSIGN_PASSWORD=${cosign_password} cosign sign ${remote_dest_image_ref} --key ${cosign_key_path} --allow-insecure-registry --yes
|
||||
if [ $? -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
@ -242,5 +266,5 @@ jq -n \
|
||||
--arg org.opencontainers.image.documentation "${description}" \
|
||||
'$ARGS.named' > ${details_file}
|
||||
|
||||
jq -c -s add ${details_file} ${trivy_out_file} > ${metafile}
|
||||
rm ${details_file} ${trivy_out_file}
|
||||
jq -c -s add ${details_file} ${trivy_out_file} ${layers_file} > ${metafile}
|
||||
rm ${details_file} ${trivy_out_file} ${layers_file}
|
||||
|
Reference in New Issue
Block a user