diff --git a/app/portainer/components/forms/git-form/git-form.controller.ts b/app/portainer/components/forms/git-form/git-form.controller.ts index f237b8e20..7033296d9 100644 --- a/app/portainer/components/forms/git-form/git-form.controller.ts +++ b/app/portainer/components/forms/git-form/git-form.controller.ts @@ -1,7 +1,7 @@ import { IFormController } from 'angular'; import { FormikErrors } from 'formik'; -import { GitFormModel } from '@/react/portainer/gitops/types'; +import { DeployMethod, GitFormModel } from '@/react/portainer/gitops/types'; import { validateGitForm } from '@/react/portainer/gitops/GitForm'; import { notifyError } from '@/portainer/services/notifications'; import { IAuthenticationService } from '@/portainer/services/types'; @@ -26,6 +26,8 @@ export default class GitFormController { createdFromCustomTemplateId?: number; + deployMethod?: DeployMethod; + /* @ngInject */ constructor( $async: (fn: () => Promise) => Promise, @@ -67,7 +69,8 @@ export default class GitFormController { this.errors = await validateGitForm( this.gitCredentials, value, - isCreatedFromCustomTemplate + isCreatedFromCustomTemplate, + this.deployMethod ); if (this.errors && Object.keys(this.errors).length > 0) { this.gitForm?.$setValidity('gitForm', false, this.gitForm); diff --git a/app/react/portainer/gitops/GitForm.stories.tsx b/app/react/portainer/gitops/GitForm.stories.tsx index 90d0a16ae..64a3c5de4 100644 --- a/app/react/portainer/gitops/GitForm.stories.tsx +++ b/app/react/portainer/gitops/GitForm.stories.tsx @@ -6,7 +6,7 @@ import { withUserProvider } from '@/react/test-utils/withUserProvider'; import { GitCredential } from '@/react/portainer/account/git-credentials/types'; import { GitForm, buildGitValidationSchema } from './GitForm'; -import { GitFormModel } from './types'; +import { DeployMethod, GitFormModel } from './types'; export default { component: GitForm, @@ -45,7 +45,7 @@ interface Args { isAdditionalFilesFieldVisible: boolean; isAuthExplanationVisible: boolean; isDockerStandalone: boolean; - deployMethod: 'compose' | 'manifest'; + deployMethod: DeployMethod; isForcePullVisible: boolean; } @@ -73,7 +73,7 @@ export function Primary({ return ( buildGitValidationSchema([], false)} + validationSchema={() => buildGitValidationSchema([], false, 'compose')} onSubmit={() => {}} > {({ values, errors, setValues }) => ( diff --git a/app/react/portainer/gitops/GitForm.tsx b/app/react/portainer/gitops/GitForm.tsx index 95a1fe357..d32174fdc 100644 --- a/app/react/portainer/gitops/GitForm.tsx +++ b/app/react/portainer/gitops/GitForm.tsx @@ -5,7 +5,7 @@ import { useState } from 'react'; import { ComposePathField } from '@/react/portainer/gitops/ComposePathField'; import { RefField } from '@/react/portainer/gitops/RefField'; import { GitFormUrlField } from '@/react/portainer/gitops/GitFormUrlField'; -import { GitFormModel } from '@/react/portainer/gitops/types'; +import { DeployMethod, GitFormModel } from '@/react/portainer/gitops/types'; import { TimeWindowDisplay } from '@/react/portainer/gitops/TimeWindowDisplay'; import { FormSection } from '@@/form-components/FormSection'; @@ -24,7 +24,7 @@ interface Props { value: GitFormModel; onChange: (value: Partial) => void; environmentType?: 'DOCKER' | 'KUBERNETES' | undefined; - deployMethod?: 'compose' | 'manifest'; + deployMethod?: DeployMethod; isDockerStandalone?: boolean; isAdditionalFilesFieldVisible?: boolean; isForcePullVisible?: boolean; @@ -142,17 +142,24 @@ export function GitForm({ export async function validateGitForm( gitCredentials: Array, formValues: GitFormModel, - isCreatedFromCustomTemplate: boolean + isCreatedFromCustomTemplate: boolean, + deployMethod: DeployMethod = 'compose' ) { return validateForm( - () => buildGitValidationSchema(gitCredentials, isCreatedFromCustomTemplate), + () => + buildGitValidationSchema( + gitCredentials, + isCreatedFromCustomTemplate, + deployMethod + ), formValues ); } export function buildGitValidationSchema( gitCredentials: Array, - isCreatedFromCustomTemplate: boolean + isCreatedFromCustomTemplate: boolean, + deployMethod: DeployMethod ): SchemaOf { return object({ RepositoryURL: string() @@ -171,7 +178,9 @@ export function buildGitValidationSchema( .required('Repository URL is required'), RepositoryReferenceName: refFieldValidation(), ComposeFilePathInRepository: string().required( - 'Compose file path is required' + deployMethod === 'compose' + ? 'Compose file path is required' + : 'Manifest file path is required' ), AdditionalFiles: array(string().required('Path is required')).default([]), RepositoryURLValid: boolean().default(false), diff --git a/app/react/portainer/gitops/types.ts b/app/react/portainer/gitops/types.ts index 546dfcfb9..074b3697a 100644 --- a/app/react/portainer/gitops/types.ts +++ b/app/react/portainer/gitops/types.ts @@ -52,6 +52,8 @@ export type GitNewCredentialModel = { export type GitAuthModel = GitCredentialsModel & GitNewCredentialModel; +export type DeployMethod = 'compose' | 'manifest'; + export interface GitFormModel extends GitAuthModel { RepositoryURL: string; RepositoryURLValid?: boolean; diff --git a/app/react/portainer/templates/custom-templates/CreateView/CreateForm.tsx b/app/react/portainer/templates/custom-templates/CreateView/CreateForm.tsx index 2da7636af..ff222f931 100644 --- a/app/react/portainer/templates/custom-templates/CreateView/CreateForm.tsx +++ b/app/react/portainer/templates/custom-templates/CreateView/CreateForm.tsx @@ -8,6 +8,7 @@ import { EnvironmentId } from '@/react/portainer/environments/types'; import { useEnvironmentDeploymentOptions } from '@/react/portainer/environments/queries/useEnvironment'; import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment'; import { isKubernetesEnvironment } from '@/react/portainer/environments/utils'; +import { DeployMethod } from '@/react/portainer/gitops/types'; import { useInitialValues } from './useInitialValues'; import { FormValues, initialBuildMethods } from './types'; @@ -23,10 +24,12 @@ export function CreateForm({ viewType: 'kube' | 'docker' | 'edge'; defaultType: StackType; }) { + const deployMethod: DeployMethod = + defaultType === StackType.Kubernetes ? 'manifest' : 'compose'; const isEdge = !environmentId; const router = useRouter(); const mutation = useCreateTemplateMutation(); - const validation = useValidation({ viewType }); + const validation = useValidation({ viewType, deployMethod }); const buildMethods = useBuildMethods(); const initialValues = useInitialValues({ diff --git a/app/react/portainer/templates/custom-templates/CreateView/useInitialValues.ts b/app/react/portainer/templates/custom-templates/CreateView/useInitialValues.ts index dc8c5696a..80cdc8d7e 100644 --- a/app/react/portainer/templates/custom-templates/CreateView/useInitialValues.ts +++ b/app/react/portainer/templates/custom-templates/CreateView/useInitialValues.ts @@ -23,6 +23,10 @@ export function useInitialValues({ const { appTemplateId, type = defaultType } = useAppTemplateParams(); + // don't make the file path 'docker-compose.yml' in a kube environment. Keep it empty with the existing 'manifest.yml' placeholder + const initialFilePathInRepository = + type === StackType.Kubernetes ? '' : 'docker-compose.yml'; + const { params: { fileContent = '' }, } = useCurrentStateAndParams(); @@ -49,7 +53,7 @@ export function useInitialValues({ RepositoryAuthentication: false, RepositoryUsername: '', RepositoryPassword: '', - ComposeFilePathInRepository: 'docker-compose.yml', + ComposeFilePathInRepository: initialFilePathInRepository, AdditionalFiles: [], RepositoryURLValid: true, TLSSkipVerify: false, diff --git a/app/react/portainer/templates/custom-templates/CreateView/useValidation.tsx b/app/react/portainer/templates/custom-templates/CreateView/useValidation.tsx index b141750e7..6b9bab0fb 100644 --- a/app/react/portainer/templates/custom-templates/CreateView/useValidation.tsx +++ b/app/react/portainer/templates/custom-templates/CreateView/useValidation.tsx @@ -10,6 +10,7 @@ import { useGitCredentials } from '@/react/portainer/account/git-credentials/git import { useCurrentUser } from '@/react/hooks/useUser'; import { useCustomTemplates } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplates'; import { edgeFieldsetValidation } from '@/react/portainer/templates/custom-templates/CreateView/EdgeSettingsFieldset.validation'; +import { DeployMethod } from '@/react/portainer/gitops/types'; import { file } from '@@/form-components/yup-file-validation'; import { @@ -22,8 +23,10 @@ import { initialBuildMethods } from './types'; export function useValidation({ viewType, + deployMethod, }: { viewType: 'kube' | 'docker' | 'edge'; + deployMethod: DeployMethod; }) { const { user } = useCurrentUser(); const gitCredentialsQuery = useGitCredentials(user.Id); @@ -58,7 +61,11 @@ export function useValidation({ Git: mixed().when('Method', { is: git.value, then: () => - buildGitValidationSchema(gitCredentialsQuery.data || [], false), + buildGitValidationSchema( + gitCredentialsQuery.data || [], + false, + deployMethod + ), }), Variables: variablesValidation(), EdgeSettings: viewType === 'edge' ? edgeFieldsetValidation() : mixed(), @@ -68,6 +75,11 @@ export function useValidation({ viewType, }) ), - [customTemplatesQuery.data, gitCredentialsQuery.data, viewType] + [ + customTemplatesQuery.data, + gitCredentialsQuery.data, + viewType, + deployMethod, + ] ); } diff --git a/app/react/portainer/templates/custom-templates/EditView/EditForm.tsx b/app/react/portainer/templates/custom-templates/EditView/EditForm.tsx index 5884deb39..22f4ce8a1 100644 --- a/app/react/portainer/templates/custom-templates/EditView/EditForm.tsx +++ b/app/react/portainer/templates/custom-templates/EditView/EditForm.tsx @@ -6,6 +6,8 @@ import { EnvironmentId } from '@/react/portainer/environments/types'; import { useEnvironmentDeploymentOptions } from '@/react/portainer/environments/queries/useEnvironment'; import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment'; import { isKubernetesEnvironment } from '@/react/portainer/environments/utils'; +import { DeployMethod } from '@/react/portainer/gitops/types'; +import { StackType } from '@/react/common/stacks/types'; import { CustomTemplate } from '../types'; import { useUpdateTemplateMutation } from '../queries/useUpdateTemplateMutation'; @@ -32,10 +34,13 @@ export function EditForm({ const router = useRouter(); const disableEditor = useDisableEditor(isGit); const mutation = useUpdateTemplateMutation(); + const deployMethod: DeployMethod = + template.Type === StackType.Kubernetes ? 'manifest' : 'compose'; const validation = useValidation({ viewType, isGit, templateId: template.Id, + deployMethod, }); const fileContentQuery = useCustomTemplateFile(template.Id); diff --git a/app/react/portainer/templates/custom-templates/EditView/useValidation.tsx b/app/react/portainer/templates/custom-templates/EditView/useValidation.tsx index d0fecd3b2..dc6a7f461 100644 --- a/app/react/portainer/templates/custom-templates/EditView/useValidation.tsx +++ b/app/react/portainer/templates/custom-templates/EditView/useValidation.tsx @@ -10,6 +10,7 @@ import { useGitCredentials } from '@/react/portainer/account/git-credentials/git import { useCurrentUser } from '@/react/hooks/useUser'; import { useCustomTemplates } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplates'; import { edgeFieldsetValidation } from '@/react/portainer/templates/custom-templates/CreateView/EdgeSettingsFieldset.validation'; +import { DeployMethod } from '@/react/portainer/gitops/types'; import { CustomTemplate } from '../types'; import { TemplateViewType } from '../useViewType'; @@ -18,10 +19,12 @@ export function useValidation({ isGit, templateId, viewType, + deployMethod, }: { isGit: boolean; templateId: CustomTemplate['Id']; viewType: TemplateViewType; + deployMethod: DeployMethod; }) { const { user } = useCurrentUser(); const gitCredentialsQuery = useGitCredentials(user.Id); @@ -47,7 +50,11 @@ export function useValidation({ FileContent: string().required('Template is required.'), Git: isGit - ? buildGitValidationSchema(gitCredentialsQuery.data || [], false) + ? buildGitValidationSchema( + gitCredentialsQuery.data || [], + false, + deployMethod + ) : mixed(), Variables: variablesValidation(), EdgeSettings: viewType === 'edge' ? edgeFieldsetValidation() : mixed(), @@ -64,6 +71,7 @@ export function useValidation({ isGit, templateId, viewType, + deployMethod, ] ); }