mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge pull request #1696 from oceanprotocol/feature/enforce-docker-containers
Enforce docker container checksum in publish form
This commit is contained in:
commit
8b108ea29f
@ -54,21 +54,22 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dockerImageCustom",
|
"name": "dockerImageCustom",
|
||||||
"label": "Docker Image URL",
|
"label": "Custom Docker Image",
|
||||||
"placeholder": "e.g. oceanprotocol/algo_dockers or https://example.com/image_path",
|
"placeholder": "e.g. oceanprotocol/algo_dockers:node-vibrant or quay.io/startx/mariadb",
|
||||||
"help": "Provide the name of a public Docker image or the full url if you have it hosted in a 3rd party repo",
|
"help": "Provide the name and the tag of a public Docker hub image or the custom image if you have it hosted in a 3rd party repository",
|
||||||
|
"type": "container",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dockerImageCustomTag",
|
"name": "dockerImageChecksum",
|
||||||
"label": "Docker Image Tag",
|
"label": "Docker Image Checksum",
|
||||||
"placeholder": "e.g. latest",
|
"placeholder": "e.g. sha256:xiXqb7Vet0FbN9q0GFMgUdi5C22wjJT0i2G6lYKC2jl6QxkKzVz7KaPDgqfTMjNF",
|
||||||
"help": "Provide the tag for your Docker image.",
|
"help": "Provide the checksum(DIGEST) of your docker image.",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dockerImageCustomEntrypoint",
|
"name": "dockerImageCustomEntrypoint",
|
||||||
"label": "Docker Entrypoint",
|
"label": "Docker Image Entrypoint",
|
||||||
"placeholder": "e.g. python $ALGO",
|
"placeholder": "e.g. python $ALGO",
|
||||||
"help": "Provide the entrypoint for your algorithm.",
|
"help": "Provide the entrypoint for your algorithm.",
|
||||||
"required": true
|
"required": true
|
||||||
|
@ -268,7 +268,7 @@ export async function getAlgorithmDatasetsForCompute(
|
|||||||
const query = generateBaseQuery(baseQueryParams)
|
const query = generateBaseQuery(baseQueryParams)
|
||||||
const computeDatasets = await queryMetadata(query, cancelToken)
|
const computeDatasets = await queryMetadata(query, cancelToken)
|
||||||
|
|
||||||
if (computeDatasets.totalResults === 0) return []
|
if (computeDatasets?.totalResults === 0) return []
|
||||||
|
|
||||||
const datasets = await transformAssetToAssetSelection(
|
const datasets = await transformAssetToAssetSelection(
|
||||||
datasetProviderUri,
|
datasetProviderUri,
|
||||||
|
@ -1,16 +1,27 @@
|
|||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import isUrl from 'is-url-superb'
|
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
async function isDockerHubImageValid(
|
export interface dockerContainerInfo {
|
||||||
|
exists: boolean
|
||||||
|
checksum: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getContainerChecksum(
|
||||||
image: string,
|
image: string,
|
||||||
tag: string
|
tag: string
|
||||||
): Promise<boolean> {
|
): Promise<dockerContainerInfo> {
|
||||||
|
const containerInfo: dockerContainerInfo = {
|
||||||
|
exists: false,
|
||||||
|
checksum: null
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
`https://dockerhub-proxy.oceanprotocol.com`,
|
`https://dockerhub-proxy.oceanprotocol.com`,
|
||||||
{ image, tag }
|
{
|
||||||
|
image,
|
||||||
|
tag
|
||||||
|
}
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
!response ||
|
!response ||
|
||||||
@ -18,46 +29,18 @@ async function isDockerHubImageValid(
|
|||||||
response.data.status !== 'success'
|
response.data.status !== 'success'
|
||||||
) {
|
) {
|
||||||
toast.error(
|
toast.error(
|
||||||
'Could not fetch docker hub image info. Please check image name and tag and try again'
|
'Could not fetch docker hub image informations. If you have it hosted in a 3rd party repository please fill in the container checksum manually.'
|
||||||
)
|
)
|
||||||
return false
|
return containerInfo
|
||||||
}
|
}
|
||||||
|
containerInfo.exists = true
|
||||||
return true
|
containerInfo.checksum = response.data.result.checksum
|
||||||
|
return containerInfo
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerInstance.error(error.message)
|
LoggerInstance.error(error.message)
|
||||||
toast.error(
|
toast.error(
|
||||||
'Could not fetch docker hub image info. Please check image name and tag and try again'
|
'Could not fetch docker hub image informations. If you have it hosted in a 3rd party repository please fill in the container checksum manually.'
|
||||||
)
|
)
|
||||||
return false
|
return containerInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function is3rdPartyImageValid(imageURL: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const response = await axios.head(imageURL)
|
|
||||||
if (!response || response.status !== 200) {
|
|
||||||
toast.error(
|
|
||||||
'Could not fetch docker image info. Please check URL and try again'
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
LoggerInstance.error(error.message)
|
|
||||||
toast.error(
|
|
||||||
'Could not fetch docker image info. Please check URL and try again'
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function validateDockerImage(
|
|
||||||
dockerImage: string,
|
|
||||||
tag: string
|
|
||||||
): Promise<boolean> {
|
|
||||||
const isValid = isUrl(dockerImage)
|
|
||||||
? await is3rdPartyImageValid(dockerImage)
|
|
||||||
: await isDockerHubImageValid(dockerImage, tag)
|
|
||||||
return isValid
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
.info {
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: calc(var(--spacer) / 2);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--background-highlight);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info li {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
margin-right: calc(var(--spacer) / 2);
|
||||||
|
color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info li.success {
|
||||||
|
color: var(--brand-alert-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info li.error {
|
||||||
|
color: var(--brand-alert-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contianer {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
line-height: var(--line-height);
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
padding-right: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.removeButton {
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
position: absolute;
|
||||||
|
top: -0.2rem;
|
||||||
|
right: 0;
|
||||||
|
font-size: var(--font-size-h3);
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--font-color-text);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
29
src/components/@shared/FormFields/ContainerInput/Info.tsx
Normal file
29
src/components/@shared/FormFields/ContainerInput/Info.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import styles from './Info.module.css'
|
||||||
|
|
||||||
|
export default function ImageInfo({
|
||||||
|
image,
|
||||||
|
tag,
|
||||||
|
valid,
|
||||||
|
handleClose
|
||||||
|
}: {
|
||||||
|
image: string
|
||||||
|
tag: string
|
||||||
|
valid: boolean
|
||||||
|
handleClose(): void
|
||||||
|
}): ReactElement {
|
||||||
|
const displayText = valid
|
||||||
|
? '✓ Image found, container checksum automatically added!'
|
||||||
|
: 'x Container checksum could not be fetched automatically, please add it manually'
|
||||||
|
return (
|
||||||
|
<div className={styles.info}>
|
||||||
|
<h3 className={styles.contianer}>{`Image: ${image} Tag: ${tag}`}</h3>
|
||||||
|
<ul>
|
||||||
|
<li className={valid ? styles.success : styles.error}>{displayText}</li>
|
||||||
|
</ul>
|
||||||
|
<button className={styles.removeButton} onClick={handleClose}>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
83
src/components/@shared/FormFields/ContainerInput/index.tsx
Normal file
83
src/components/@shared/FormFields/ContainerInput/index.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import React, { ReactElement, useState } from 'react'
|
||||||
|
import { useField, useFormikContext } from 'formik'
|
||||||
|
import UrlInput from '../URLInput'
|
||||||
|
import { InputProps } from '@shared/FormInput'
|
||||||
|
import { FormPublishData } from 'src/components/Publish/_types'
|
||||||
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
|
import ImageInfo from './Info'
|
||||||
|
import { getContainerChecksum } from '@utils/docker'
|
||||||
|
|
||||||
|
export default function ContainerInput(props: InputProps): ReactElement {
|
||||||
|
const [field] = useField(props.name)
|
||||||
|
const [fieldChecksum, metaChecksum, helpersChecksum] = useField(
|
||||||
|
'metadata.dockerImageCustomChecksum'
|
||||||
|
)
|
||||||
|
|
||||||
|
const { values, setFieldError, setFieldValue } =
|
||||||
|
useFormikContext<FormPublishData>()
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [isValid, setIsValid] = useState(false)
|
||||||
|
const [checked, setChecked] = useState(false)
|
||||||
|
|
||||||
|
async function handleValidation(e: React.SyntheticEvent, container: string) {
|
||||||
|
e.preventDefault()
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
const parsedContainerValue = container?.split(':')
|
||||||
|
const imageName =
|
||||||
|
parsedContainerValue?.length > 1
|
||||||
|
? parsedContainerValue?.slice(0, -1).join(':')
|
||||||
|
: parsedContainerValue[0]
|
||||||
|
const tag =
|
||||||
|
parsedContainerValue?.length > 1 ? parsedContainerValue?.at(-1) : ''
|
||||||
|
const containerInfo = await getContainerChecksum(imageName, tag)
|
||||||
|
setFieldValue('metadata.dockerImageCustom', imageName)
|
||||||
|
setFieldValue('metadata.dockerImageCustomTag', tag)
|
||||||
|
setChecked(true)
|
||||||
|
if (containerInfo.checksum) {
|
||||||
|
setFieldValue(
|
||||||
|
'metadata.dockerImageCustomChecksum',
|
||||||
|
containerInfo.checksum
|
||||||
|
)
|
||||||
|
helpersChecksum.setTouched(false)
|
||||||
|
setIsValid(true)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setFieldError(`${field.name}[0].url`, error.message)
|
||||||
|
LoggerInstance.error(error.message)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
setFieldValue('metadata.dockerImageCustom', '')
|
||||||
|
setFieldValue('metadata.dockerImageCustomTag', '')
|
||||||
|
setFieldValue('metadata.dockerImageCustomChecksum', '')
|
||||||
|
setChecked(false)
|
||||||
|
setIsValid(false)
|
||||||
|
helpersChecksum.setTouched(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{checked ? (
|
||||||
|
<ImageInfo
|
||||||
|
image={values.metadata.dockerImageCustom}
|
||||||
|
tag={values.metadata.dockerImageCustomTag}
|
||||||
|
valid={isValid}
|
||||||
|
handleClose={handleClose}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<UrlInput
|
||||||
|
submitText="Use"
|
||||||
|
{...props}
|
||||||
|
name={`${field.name}[0].url`}
|
||||||
|
checkUrl={false}
|
||||||
|
isLoading={isLoading}
|
||||||
|
handleButtonClick={handleValidation}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -12,12 +12,14 @@ export default function URLInput({
|
|||||||
handleButtonClick,
|
handleButtonClick,
|
||||||
isLoading,
|
isLoading,
|
||||||
name,
|
name,
|
||||||
|
checkUrl,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
submitText: string
|
submitText: string
|
||||||
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
name: string
|
name: string
|
||||||
|
checkUrl?: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const [field, meta] = useField(name)
|
const [field, meta] = useField(name)
|
||||||
const [isButtonDisabled, setIsButtonDisabled] = useState(true)
|
const [isButtonDisabled, setIsButtonDisabled] = useState(true)
|
||||||
@ -28,7 +30,7 @@ export default function URLInput({
|
|||||||
setIsButtonDisabled(
|
setIsButtonDisabled(
|
||||||
!field?.value ||
|
!field?.value ||
|
||||||
field.value === '' ||
|
field.value === '' ||
|
||||||
!isUrl(field.value) ||
|
(checkUrl && !isUrl(field.value)) ||
|
||||||
field.value.includes('javascript:') ||
|
field.value.includes('javascript:') ||
|
||||||
meta?.error
|
meta?.error
|
||||||
)
|
)
|
||||||
|
@ -11,6 +11,7 @@ import AssetSelection, {
|
|||||||
} from '../FormFields/AssetSelection'
|
} from '../FormFields/AssetSelection'
|
||||||
import Nft from '../FormFields/Nft'
|
import Nft from '../FormFields/Nft'
|
||||||
import InputRadio from './InputRadio'
|
import InputRadio from './InputRadio'
|
||||||
|
import ContainerInput from '@shared/FormFields/ContainerInput'
|
||||||
import TagsAutoComplete from './TagsAutoComplete'
|
import TagsAutoComplete from './TagsAutoComplete'
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
const cx = classNames.bind(styles)
|
||||||
@ -108,6 +109,8 @@ export default function InputElement({
|
|||||||
)
|
)
|
||||||
case 'files':
|
case 'files':
|
||||||
return <FilesInput {...field} {...props} />
|
return <FilesInput {...field} {...props} />
|
||||||
|
case 'container':
|
||||||
|
return <ContainerInput {...field} {...props} />
|
||||||
case 'providerUrl':
|
case 'providerUrl':
|
||||||
return <CustomProvider {...field} {...props} />
|
return <CustomProvider {...field} {...props} />
|
||||||
case 'nft':
|
case 'nft':
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BoxSelectionOption } from '@shared/FormFields/BoxSelection'
|
import { BoxSelectionOption } from '@shared/FormFields/BoxSelection'
|
||||||
import Input from '@shared/FormInput'
|
import Input from '@shared/FormInput'
|
||||||
import { Field, useFormikContext } from 'formik'
|
import { Field, useField, useFormikContext } from 'formik'
|
||||||
import React, { ReactElement, useEffect } from 'react'
|
import React, { ReactElement, useEffect } from 'react'
|
||||||
import content from '../../../../content/publish/form.json'
|
import content from '../../../../content/publish/form.json'
|
||||||
import { FormPublishData } from '../_types'
|
import { FormPublishData } from '../_types'
|
||||||
@ -23,6 +23,8 @@ export default function MetadataFields(): ReactElement {
|
|||||||
// connect with Form state, use for conditional field rendering
|
// connect with Form state, use for conditional field rendering
|
||||||
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
||||||
|
|
||||||
|
const [field, meta] = useField('metadata.dockerImageCustomChecksum')
|
||||||
|
|
||||||
// BoxSelection component is not a Formik component
|
// BoxSelection component is not a Formik component
|
||||||
// so we need to handle checked state manually.
|
// so we need to handle checked state manually.
|
||||||
const assetTypeOptions: BoxSelectionOption[] = [
|
const assetTypeOptions: BoxSelectionOption[] = [
|
||||||
@ -124,11 +126,14 @@ export default function MetadataFields(): ReactElement {
|
|||||||
/>
|
/>
|
||||||
<Field
|
<Field
|
||||||
{...getFieldContent(
|
{...getFieldContent(
|
||||||
'dockerImageCustomTag',
|
'dockerImageChecksum',
|
||||||
content.metadata.fields
|
content.metadata.fields
|
||||||
)}
|
)}
|
||||||
component={Input}
|
component={Input}
|
||||||
name="metadata.dockerImageCustomTag"
|
name="metadata.dockerImageCustomChecksum"
|
||||||
|
disabled={
|
||||||
|
values.metadata.dockerImageCustomChecksum && !meta.touched
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Field
|
<Field
|
||||||
{...getFieldContent(
|
{...getFieldContent(
|
||||||
|
@ -96,17 +96,15 @@ export const initialValues: FormPublishData = {
|
|||||||
export const algorithmContainerPresets: MetadataAlgorithmContainer[] = [
|
export const algorithmContainerPresets: MetadataAlgorithmContainer[] = [
|
||||||
{
|
{
|
||||||
image: 'node',
|
image: 'node',
|
||||||
tag: '18.6.0', // TODO: Put this back to latest once merging the PR that fetches the container digest from docker hub via dockerhub-proxy
|
tag: 'latest',
|
||||||
entrypoint: 'node $ALGO',
|
entrypoint: 'node $ALGO',
|
||||||
checksum:
|
checksum: ''
|
||||||
'sha256:c60726646352202d95de70d9e8393c15f382f8c6074afc5748b7e570ccd5995f'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: 'python',
|
image: 'python',
|
||||||
tag: '3.10.5', // TODO: Put this back to latest once merging the PR that fetches the container digest from docker hub via dockerhub-proxy
|
tag: 'latest',
|
||||||
entrypoint: 'python $ALGO',
|
entrypoint: 'python $ALGO',
|
||||||
checksum:
|
checksum: ''
|
||||||
'sha256:607635763e54907fd75397fedfeb83890e62a0f9b54a1d99d27d748c5d269be4'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -26,20 +26,24 @@ import {
|
|||||||
publisherMarketFixedSwapFee
|
publisherMarketFixedSwapFee
|
||||||
} from '../../../app.config'
|
} from '../../../app.config'
|
||||||
import { sanitizeUrl } from '@utils/url'
|
import { sanitizeUrl } from '@utils/url'
|
||||||
|
import { getContainerChecksum } from '@utils/docker'
|
||||||
|
|
||||||
function getUrlFileExtension(fileUrl: string): string {
|
function getUrlFileExtension(fileUrl: string): string {
|
||||||
const splittedFileUrl = fileUrl.split('.')
|
const splittedFileUrl = fileUrl.split('.')
|
||||||
return splittedFileUrl[splittedFileUrl.length - 1]
|
return splittedFileUrl[splittedFileUrl.length - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAlgorithmContainerPreset(
|
async function getAlgorithmContainerPreset(
|
||||||
dockerImage: string
|
dockerImage: string
|
||||||
): MetadataAlgorithmContainer {
|
): Promise<MetadataAlgorithmContainer> {
|
||||||
if (dockerImage === '') return
|
if (dockerImage === '') return
|
||||||
|
|
||||||
const preset = algorithmContainerPresets.find(
|
const preset = algorithmContainerPresets.find(
|
||||||
(preset) => `${preset.image}:${preset.tag}` === dockerImage
|
(preset) => `${preset.image}:${preset.tag}` === dockerImage
|
||||||
)
|
)
|
||||||
|
preset.checksum = await (
|
||||||
|
await getContainerChecksum(preset.image, preset.tag)
|
||||||
|
).checksum
|
||||||
return preset
|
return preset
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +84,11 @@ export async function transformPublishFormToDdo(
|
|||||||
const currentTime = dateToStringNoMS(new Date())
|
const currentTime = dateToStringNoMS(new Date())
|
||||||
const isPreview = !datatokenAddress && !nftAddress
|
const isPreview = !datatokenAddress && !nftAddress
|
||||||
|
|
||||||
|
const algorithmContainerPresets =
|
||||||
|
type === 'algorithm' && dockerImage !== '' && dockerImage !== 'custom'
|
||||||
|
? await getAlgorithmContainerPreset(dockerImage)
|
||||||
|
: null
|
||||||
|
|
||||||
// Transform from files[0].url to string[] assuming only 1 file
|
// Transform from files[0].url to string[] assuming only 1 file
|
||||||
const filesTransformed = files?.length &&
|
const filesTransformed = files?.length &&
|
||||||
files[0].valid && [sanitizeUrl(files[0].url)]
|
files[0].valid && [sanitizeUrl(files[0].url)]
|
||||||
@ -110,20 +119,19 @@ export async function transformPublishFormToDdo(
|
|||||||
entrypoint:
|
entrypoint:
|
||||||
dockerImage === 'custom'
|
dockerImage === 'custom'
|
||||||
? dockerImageCustomEntrypoint
|
? dockerImageCustomEntrypoint
|
||||||
: getAlgorithmContainerPreset(dockerImage).entrypoint,
|
: algorithmContainerPresets.entrypoint,
|
||||||
image:
|
image:
|
||||||
dockerImage === 'custom'
|
dockerImage === 'custom'
|
||||||
? dockerImageCustom
|
? dockerImageCustom
|
||||||
: getAlgorithmContainerPreset(dockerImage).image,
|
: algorithmContainerPresets.image,
|
||||||
tag:
|
tag:
|
||||||
dockerImage === 'custom'
|
dockerImage === 'custom'
|
||||||
? dockerImageCustomTag
|
? dockerImageCustomTag
|
||||||
: getAlgorithmContainerPreset(dockerImage).tag,
|
: algorithmContainerPresets.tag,
|
||||||
checksum:
|
checksum:
|
||||||
dockerImage === 'custom'
|
dockerImage === 'custom'
|
||||||
? // ? dockerImageCustomChecksum
|
? dockerImageCustomChecksum
|
||||||
''
|
: algorithmContainerPresets.checksum
|
||||||
: getAlgorithmContainerPreset(dockerImage).checksum
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user