mirror of
https://github.com/harness/drone.git
synced 2025-05-21 11:29:52 +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",
|
||||
"@harnessio/design-system": "^2.1.1",
|
||||
"@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",
|
||||
"@tanstack/react-query": "4.20.4",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
|
@ -14,9 +14,10 @@
|
||||
* 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 { HARServiceAPIClient } from '@harnessio/react-har-service-client'
|
||||
import { SSCAManagerAPIClient } from '@harnessio/react-ssca-manager-client'
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
|
||||
import { StringsContextProvider } from '@ar/frameworks/strings/StringsContextProvider'
|
||||
@ -57,10 +58,10 @@ export default function ChildApp(props: PropsWithChildren<MFEAppProps>): React.R
|
||||
} = props
|
||||
|
||||
const { ModalProvider } = customComponents
|
||||
|
||||
const appStoreData = React.useContext(parentContextObj.appStoreContext)
|
||||
useRef<HARServiceAPIClient>(
|
||||
new HARServiceAPIClient({
|
||||
|
||||
const apiClientOptions = useMemo(
|
||||
() => ({
|
||||
responseInterceptor: (response: Response): Response => {
|
||||
if (!response.ok && response.status === 401) {
|
||||
on401()
|
||||
@ -71,7 +72,7 @@ export default function ChildApp(props: PropsWithChildren<MFEAppProps>): React.R
|
||||
urlInterceptor: (url: string) => {
|
||||
return customUtils.getApiBaseUrl(url)
|
||||
},
|
||||
requestInterceptor(request) {
|
||||
requestInterceptor(request: Request) {
|
||||
request.headers.delete('Authorization')
|
||||
// add custom headers if available
|
||||
const customHeader = customUtils.getCustomHeaders()
|
||||
@ -80,9 +81,13 @@ export default function ChildApp(props: PropsWithChildren<MFEAppProps>): React.R
|
||||
})
|
||||
return request
|
||||
}
|
||||
})
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
useRef<HARServiceAPIClient>(new HARServiceAPIClient(apiClientOptions))
|
||||
useRef<SSCAManagerAPIClient>(new SSCAManagerAPIClient(apiClientOptions))
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (typeof appStoreData.updateAppStore === 'function' && parent !== Parent.Enterprise) {
|
||||
|
@ -100,6 +100,7 @@ export default function DockerVersionOverviewCards() {
|
||||
artifactId: responseData.sbomDetails?.artifactId
|
||||
})
|
||||
}}
|
||||
provenanceId={defaultTo(responseData.slsaDetails?.provenanceId, '')}
|
||||
className={css.card}
|
||||
totalComponents={defaultTo(responseData.sbomDetails?.componentsCount, 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
|
||||
export declare const column: string
|
||||
export declare const container: string
|
||||
export declare const downloadSlsaBtn: string
|
||||
export declare const primaryColumn: string
|
||||
|
@ -16,10 +16,12 @@
|
||||
|
||||
import React from 'react'
|
||||
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 { killEvent } from '@ar/common/utils'
|
||||
import { useStrings } from '@ar/frameworks/strings'
|
||||
import useDownloadSLSAProvenance from '@ar/pages/version-details/hooks/useDownloadSLSAProvenance'
|
||||
|
||||
import SecurityItem from '../SecurityTestsCard/SecurityItem'
|
||||
import { SecurityTestSatus } from '../SecurityTestsCard/types'
|
||||
@ -33,13 +35,16 @@ interface SupplyChainCardProps {
|
||||
allowListCount: number
|
||||
denyListCount: number
|
||||
sbomScore: string | number
|
||||
provenanceId: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
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 { download, loading } = useDownloadSLSAProvenance()
|
||||
|
||||
return (
|
||||
<Card className={className} onClick={onClick}>
|
||||
<Layout.Vertical>
|
||||
@ -59,13 +64,18 @@ export default function SupplyChainCard(props: SupplyChainCardProps) {
|
||||
value={sbomScore}
|
||||
status={SecurityTestSatus.Green}
|
||||
/>
|
||||
<Text
|
||||
flex={{ alignItems: 'center' }}
|
||||
font={{ variation: FontVariation.SMALL }}
|
||||
<Button
|
||||
className={css.downloadSlsaBtn}
|
||||
size={ButtonSize.SMALL}
|
||||
rightIcon="download-manifests"
|
||||
iconProps={{ size: 18 }}>
|
||||
variation={ButtonVariation.LINK}
|
||||
loading={loading}
|
||||
onClick={evt => {
|
||||
killEvent(evt)
|
||||
download(provenanceId)
|
||||
}}>
|
||||
{getString('versionDetails.cards.supplyChain.slsaProvenance')}
|
||||
</Text>
|
||||
</Button>
|
||||
</Layout.Vertical>
|
||||
</Layout.Horizontal>
|
||||
<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
|
||||
sbomScore: SBOM Score
|
||||
slsaProvenance: SLSA Provenance
|
||||
provenanceDataNotAvailable: Provenance data is not available
|
||||
allowList: Allow List
|
||||
denyListViolation: Deny List Violation
|
||||
container:
|
||||
|
@ -161,6 +161,7 @@ export interface StringsMap {
|
||||
'versionDetails.cards.securityTests.totalCount': string
|
||||
'versionDetails.cards.supplyChain.allowList': string
|
||||
'versionDetails.cards.supplyChain.denyListViolation': string
|
||||
'versionDetails.cards.supplyChain.provenanceDataNotAvailable': string
|
||||
'versionDetails.cards.supplyChain.sbomScore': string
|
||||
'versionDetails.cards.supplyChain.slsaProvenance': 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"
|
||||
integrity sha512-+vugtcJR47pQZj2IMYRt7XSNpjW/m9iSeLjQMG2Hja3Hq9J+zfDsXh2aRor3ICrLSrkQtRWpBbWn+WmsoN3lVg==
|
||||
|
||||
"@harnessio/react-har-service-client@^0.0.22":
|
||||
version "0.0.22"
|
||||
resolved "https://registry.yarnpkg.com/@harnessio/react-har-service-client/-/react-har-service-client-0.0.22.tgz#f946ef4378fecc5ecbffa46ea664446b93b01f79"
|
||||
integrity sha512-MzTcA3xcaQbuYVDe9v5gPaHSCUs+7gpLt4F5/Bh26hbhzPSpGK2nxqh37sxIq3fVNkpGWXpScrnCxV1FDGNHvw==
|
||||
"@harnessio/react-har-service-client@^0.0.23":
|
||||
version "0.0.23"
|
||||
resolved "https://registry.yarnpkg.com/@harnessio/react-har-service-client/-/react-har-service-client-0.0.23.tgz#ea4f5705155b9d7a87a475f5a45adb9d72c2d8ab"
|
||||
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":
|
||||
version "4.1.2"
|
||||
|
Loading…
Reference in New Issue
Block a user