mirror of
https://github.com/harness/drone.git
synced 2025-05-22 03:49:54 +08:00
feat: [AH-341]: support download SLSA report from artifact registry (#2771)
* feat: [AH-341]: update spelling mistake * Merge branch 'feat-AH-341' of https://git0.harness.io/l7B_kbSEQD2wjrM7PShm5w/PROD/Harness_Commons/gitness into feat-AH-341 * feat: [AH-341]: update api schema to fix type errors * feat: [AH-341]: support download SLSA report from artifact registry * feat: [AH-341]: update api schema to fix type errors * feat: [AH-341]: support download SLSA report from artifact registry
This commit is contained in:
parent
f62a6e5e68
commit
8b53e5b92a
@ -51,7 +51,8 @@
|
|||||||
"@codemirror/view": "^6.9.6",
|
"@codemirror/view": "^6.9.6",
|
||||||
"@harnessio/design-system": "^2.1.1",
|
"@harnessio/design-system": "^2.1.1",
|
||||||
"@harnessio/icons": "^2.1.7",
|
"@harnessio/icons": "^2.1.7",
|
||||||
"@harnessio/react-har-service-client": "^0.0.22",
|
"@harnessio/react-har-service-client": "^0.0.23",
|
||||||
|
"@harnessio/react-ssca-manager-client": "^0.65.0",
|
||||||
"@harnessio/uicore": "^4.1.2",
|
"@harnessio/uicore": "^4.1.2",
|
||||||
"@tanstack/react-query": "4.20.4",
|
"@tanstack/react-query": "4.20.4",
|
||||||
"@types/dompurify": "^3.0.2",
|
"@types/dompurify": "^3.0.2",
|
||||||
|
@ -14,9 +14,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { PropsWithChildren, Suspense, useEffect, useRef } from 'react'
|
import React, { PropsWithChildren, Suspense, useEffect, useMemo, useRef } from 'react'
|
||||||
import { Page } from '@harnessio/uicore'
|
import { Page } from '@harnessio/uicore'
|
||||||
import { HARServiceAPIClient } from '@harnessio/react-har-service-client'
|
import { HARServiceAPIClient } from '@harnessio/react-har-service-client'
|
||||||
|
import { SSCAManagerAPIClient } from '@harnessio/react-ssca-manager-client'
|
||||||
import { QueryClientProvider } from '@tanstack/react-query'
|
import { QueryClientProvider } from '@tanstack/react-query'
|
||||||
|
|
||||||
import { StringsContextProvider } from '@ar/frameworks/strings/StringsContextProvider'
|
import { StringsContextProvider } from '@ar/frameworks/strings/StringsContextProvider'
|
||||||
@ -57,10 +58,10 @@ export default function ChildApp(props: PropsWithChildren<MFEAppProps>): React.R
|
|||||||
} = props
|
} = props
|
||||||
|
|
||||||
const { ModalProvider } = customComponents
|
const { ModalProvider } = customComponents
|
||||||
|
|
||||||
const appStoreData = React.useContext(parentContextObj.appStoreContext)
|
const appStoreData = React.useContext(parentContextObj.appStoreContext)
|
||||||
useRef<HARServiceAPIClient>(
|
|
||||||
new HARServiceAPIClient({
|
const apiClientOptions = useMemo(
|
||||||
|
() => ({
|
||||||
responseInterceptor: (response: Response): Response => {
|
responseInterceptor: (response: Response): Response => {
|
||||||
if (!response.ok && response.status === 401) {
|
if (!response.ok && response.status === 401) {
|
||||||
on401()
|
on401()
|
||||||
@ -71,7 +72,7 @@ export default function ChildApp(props: PropsWithChildren<MFEAppProps>): React.R
|
|||||||
urlInterceptor: (url: string) => {
|
urlInterceptor: (url: string) => {
|
||||||
return customUtils.getApiBaseUrl(url)
|
return customUtils.getApiBaseUrl(url)
|
||||||
},
|
},
|
||||||
requestInterceptor(request) {
|
requestInterceptor(request: Request) {
|
||||||
request.headers.delete('Authorization')
|
request.headers.delete('Authorization')
|
||||||
// add custom headers if available
|
// add custom headers if available
|
||||||
const customHeader = customUtils.getCustomHeaders()
|
const customHeader = customUtils.getCustomHeaders()
|
||||||
@ -80,9 +81,13 @@ export default function ChildApp(props: PropsWithChildren<MFEAppProps>): React.R
|
|||||||
})
|
})
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useRef<HARServiceAPIClient>(new HARServiceAPIClient(apiClientOptions))
|
||||||
|
useRef<SSCAManagerAPIClient>(new SSCAManagerAPIClient(apiClientOptions))
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => () => {
|
() => () => {
|
||||||
if (typeof appStoreData.updateAppStore === 'function' && parent !== Parent.Enterprise) {
|
if (typeof appStoreData.updateAppStore === 'function' && parent !== Parent.Enterprise) {
|
||||||
|
@ -100,6 +100,7 @@ export default function DockerVersionOverviewCards() {
|
|||||||
artifactId: responseData.sbomDetails?.artifactId
|
artifactId: responseData.sbomDetails?.artifactId
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
provenanceId={defaultTo(responseData.slsaDetails?.provenanceId, '')}
|
||||||
className={css.card}
|
className={css.card}
|
||||||
totalComponents={defaultTo(responseData.sbomDetails?.componentsCount, 0)}
|
totalComponents={defaultTo(responseData.sbomDetails?.componentsCount, 0)}
|
||||||
allowListCount={defaultTo(responseData.sbomDetails?.allowListViolations, 0)}
|
allowListCount={defaultTo(responseData.sbomDetails?.allowListViolations, 0)}
|
||||||
|
@ -25,3 +25,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.downloadSlsaBtn {
|
||||||
|
--font-size: 12px !important;
|
||||||
|
--padding: 0 !important;
|
||||||
|
}
|
||||||
|
@ -18,4 +18,5 @@
|
|||||||
// This is an auto-generated file
|
// This is an auto-generated file
|
||||||
export declare const column: string
|
export declare const column: string
|
||||||
export declare const container: string
|
export declare const container: string
|
||||||
|
export declare const downloadSlsaBtn: string
|
||||||
export declare const primaryColumn: string
|
export declare const primaryColumn: string
|
||||||
|
@ -16,10 +16,12 @@
|
|||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { Card, Layout, Text } from '@harnessio/uicore'
|
import { Button, ButtonSize, ButtonVariation, Card, Layout, Text } from '@harnessio/uicore'
|
||||||
import { Color, FontVariation } from '@harnessio/design-system'
|
import { Color, FontVariation } from '@harnessio/design-system'
|
||||||
|
|
||||||
|
import { killEvent } from '@ar/common/utils'
|
||||||
import { useStrings } from '@ar/frameworks/strings'
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
|
import useDownloadSLSAProvenance from '@ar/pages/version-details/hooks/useDownloadSLSAProvenance'
|
||||||
|
|
||||||
import SecurityItem from '../SecurityTestsCard/SecurityItem'
|
import SecurityItem from '../SecurityTestsCard/SecurityItem'
|
||||||
import { SecurityTestSatus } from '../SecurityTestsCard/types'
|
import { SecurityTestSatus } from '../SecurityTestsCard/types'
|
||||||
@ -33,13 +35,16 @@ interface SupplyChainCardProps {
|
|||||||
allowListCount: number
|
allowListCount: number
|
||||||
denyListCount: number
|
denyListCount: number
|
||||||
sbomScore: string | number
|
sbomScore: string | number
|
||||||
|
provenanceId: string
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function SupplyChainCard(props: SupplyChainCardProps) {
|
export default function SupplyChainCard(props: SupplyChainCardProps) {
|
||||||
const { title, totalComponents, allowListCount, denyListCount, className, sbomScore, onClick } = props
|
const { title, totalComponents, allowListCount, denyListCount, className, sbomScore, onClick, provenanceId } = props
|
||||||
const { getString } = useStrings()
|
const { getString } = useStrings()
|
||||||
|
|
||||||
|
const { download, loading } = useDownloadSLSAProvenance()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={className} onClick={onClick}>
|
<Card className={className} onClick={onClick}>
|
||||||
<Layout.Vertical>
|
<Layout.Vertical>
|
||||||
@ -59,13 +64,18 @@ export default function SupplyChainCard(props: SupplyChainCardProps) {
|
|||||||
value={sbomScore}
|
value={sbomScore}
|
||||||
status={SecurityTestSatus.Green}
|
status={SecurityTestSatus.Green}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Button
|
||||||
flex={{ alignItems: 'center' }}
|
className={css.downloadSlsaBtn}
|
||||||
font={{ variation: FontVariation.SMALL }}
|
size={ButtonSize.SMALL}
|
||||||
rightIcon="download-manifests"
|
rightIcon="download-manifests"
|
||||||
iconProps={{ size: 18 }}>
|
variation={ButtonVariation.LINK}
|
||||||
|
loading={loading}
|
||||||
|
onClick={evt => {
|
||||||
|
killEvent(evt)
|
||||||
|
download(provenanceId)
|
||||||
|
}}>
|
||||||
{getString('versionDetails.cards.supplyChain.slsaProvenance')}
|
{getString('versionDetails.cards.supplyChain.slsaProvenance')}
|
||||||
</Text>
|
</Button>
|
||||||
</Layout.Vertical>
|
</Layout.Vertical>
|
||||||
</Layout.Horizontal>
|
</Layout.Horizontal>
|
||||||
<Layout.Horizontal className={css.container}>
|
<Layout.Horizontal className={css.container}>
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { defaultTo } from 'lodash-es'
|
||||||
|
import { useToaster } from '@harnessio/uicore'
|
||||||
|
import { getProvenance } from '@harnessio/react-ssca-manager-client'
|
||||||
|
|
||||||
|
import { useAppStore } from '@ar/hooks'
|
||||||
|
import { useStrings } from '@ar/frameworks/strings'
|
||||||
|
import { downloadRawFile } from '@ar/utils/downloadRawFile'
|
||||||
|
|
||||||
|
export default function useDownloadSLSAProvenance() {
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const { scope } = useAppStore()
|
||||||
|
const { showError } = useToaster()
|
||||||
|
const { getString } = useStrings()
|
||||||
|
|
||||||
|
const download = async (provenanceId: string) => {
|
||||||
|
setLoading(true)
|
||||||
|
return getProvenance({
|
||||||
|
org: defaultTo(scope.orgIdentifier, ''),
|
||||||
|
project: defaultTo(scope.projectIdentifier, ''),
|
||||||
|
provenance: provenanceId
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
const content = defaultTo(data.content.provenance, '')
|
||||||
|
if (!content) {
|
||||||
|
throw new Error(getString('versionDetails.cards.supplyChain.provenanceDataNotAvailable'))
|
||||||
|
}
|
||||||
|
return downloadRawFile(content, `provenance_${provenanceId}.json`)
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
showError(defaultTo(err?.message, err))
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { download, loading }
|
||||||
|
}
|
@ -25,6 +25,7 @@ cards:
|
|||||||
totalComponents: Total Dependencies
|
totalComponents: Total Dependencies
|
||||||
sbomScore: SBOM Score
|
sbomScore: SBOM Score
|
||||||
slsaProvenance: SLSA Provenance
|
slsaProvenance: SLSA Provenance
|
||||||
|
provenanceDataNotAvailable: Provenance data is not available
|
||||||
allowList: Allow List
|
allowList: Allow List
|
||||||
denyListViolation: Deny List Violation
|
denyListViolation: Deny List Violation
|
||||||
container:
|
container:
|
||||||
|
@ -161,6 +161,7 @@ export interface StringsMap {
|
|||||||
'versionDetails.cards.securityTests.totalCount': string
|
'versionDetails.cards.securityTests.totalCount': string
|
||||||
'versionDetails.cards.supplyChain.allowList': string
|
'versionDetails.cards.supplyChain.allowList': string
|
||||||
'versionDetails.cards.supplyChain.denyListViolation': string
|
'versionDetails.cards.supplyChain.denyListViolation': string
|
||||||
|
'versionDetails.cards.supplyChain.provenanceDataNotAvailable': string
|
||||||
'versionDetails.cards.supplyChain.sbomScore': string
|
'versionDetails.cards.supplyChain.sbomScore': string
|
||||||
'versionDetails.cards.supplyChain.slsaProvenance': string
|
'versionDetails.cards.supplyChain.slsaProvenance': string
|
||||||
'versionDetails.cards.supplyChain.title': string
|
'versionDetails.cards.supplyChain.title': string
|
||||||
|
34
web/src/ar/utils/downloadRawFile.ts
Normal file
34
web/src/ar/utils/downloadRawFile.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Harness, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const downloadRawFile = (content: string, filename: string, fileType = 'text/json') => {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const url = URL.createObjectURL(new Blob([content], { type: fileType }))
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = filename
|
||||||
|
a.click()
|
||||||
|
setTimeout(() => {
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
a.remove()
|
||||||
|
}, 150)
|
||||||
|
resolve()
|
||||||
|
} catch (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -1654,10 +1654,15 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@harnessio/icons/-/icons-2.1.7.tgz#21c37bad4291e1ed4ea730f6ed1f7612934e8b3f"
|
resolved "https://registry.yarnpkg.com/@harnessio/icons/-/icons-2.1.7.tgz#21c37bad4291e1ed4ea730f6ed1f7612934e8b3f"
|
||||||
integrity sha512-+vugtcJR47pQZj2IMYRt7XSNpjW/m9iSeLjQMG2Hja3Hq9J+zfDsXh2aRor3ICrLSrkQtRWpBbWn+WmsoN3lVg==
|
integrity sha512-+vugtcJR47pQZj2IMYRt7XSNpjW/m9iSeLjQMG2Hja3Hq9J+zfDsXh2aRor3ICrLSrkQtRWpBbWn+WmsoN3lVg==
|
||||||
|
|
||||||
"@harnessio/react-har-service-client@^0.0.22":
|
"@harnessio/react-har-service-client@^0.0.23":
|
||||||
version "0.0.22"
|
version "0.0.23"
|
||||||
resolved "https://registry.yarnpkg.com/@harnessio/react-har-service-client/-/react-har-service-client-0.0.22.tgz#f946ef4378fecc5ecbffa46ea664446b93b01f79"
|
resolved "https://registry.yarnpkg.com/@harnessio/react-har-service-client/-/react-har-service-client-0.0.23.tgz#ea4f5705155b9d7a87a475f5a45adb9d72c2d8ab"
|
||||||
integrity sha512-MzTcA3xcaQbuYVDe9v5gPaHSCUs+7gpLt4F5/Bh26hbhzPSpGK2nxqh37sxIq3fVNkpGWXpScrnCxV1FDGNHvw==
|
integrity sha512-fXcW3WylHLCpPf1Ce/z1GoKcy/1jPk7tzlXMUzqTnooFlnhpOPV5fXOpVyGP8i8mmKIW17vsLLedgf+LgRhD3w==
|
||||||
|
|
||||||
|
"@harnessio/react-ssca-manager-client@^0.65.0":
|
||||||
|
version "0.65.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@harnessio/react-ssca-manager-client/-/react-ssca-manager-client-0.65.0.tgz#8088869e282c5268bf1fefb9715652e0fc1a8940"
|
||||||
|
integrity sha512-sNLDf1lyBfdzD9TqlrsMib61YffrEpb5WJwhRKuPGKD9Po0peAqE6NGLHVFbyra4tRGvvg8gnNpzzp4hMBYeCQ==
|
||||||
|
|
||||||
"@harnessio/uicore@^4.1.2":
|
"@harnessio/uicore@^4.1.2":
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
|
Loading…
Reference in New Issue
Block a user