fix(gitops): manifest validation warning [EE-6859] (#11664)

pull/11820/head
Ali 2024-05-13 15:09:25 +12:00 committed by GitHub
parent a0ab82b866
commit 55667a878a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 62 additions and 16 deletions

View File

@ -1,7 +1,7 @@
import { IFormController } from 'angular'; import { IFormController } from 'angular';
import { FormikErrors } from 'formik'; 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 { validateGitForm } from '@/react/portainer/gitops/GitForm';
import { notifyError } from '@/portainer/services/notifications'; import { notifyError } from '@/portainer/services/notifications';
import { IAuthenticationService } from '@/portainer/services/types'; import { IAuthenticationService } from '@/portainer/services/types';
@ -26,6 +26,8 @@ export default class GitFormController {
createdFromCustomTemplateId?: number; createdFromCustomTemplateId?: number;
deployMethod?: DeployMethod;
/* @ngInject */ /* @ngInject */
constructor( constructor(
$async: <T>(fn: () => Promise<T>) => Promise<T>, $async: <T>(fn: () => Promise<T>) => Promise<T>,
@ -67,7 +69,8 @@ export default class GitFormController {
this.errors = await validateGitForm( this.errors = await validateGitForm(
this.gitCredentials, this.gitCredentials,
value, value,
isCreatedFromCustomTemplate isCreatedFromCustomTemplate,
this.deployMethod
); );
if (this.errors && Object.keys(this.errors).length > 0) { if (this.errors && Object.keys(this.errors).length > 0) {
this.gitForm?.$setValidity('gitForm', false, this.gitForm); this.gitForm?.$setValidity('gitForm', false, this.gitForm);

View File

@ -6,7 +6,7 @@ import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { GitCredential } from '@/react/portainer/account/git-credentials/types'; import { GitCredential } from '@/react/portainer/account/git-credentials/types';
import { GitForm, buildGitValidationSchema } from './GitForm'; import { GitForm, buildGitValidationSchema } from './GitForm';
import { GitFormModel } from './types'; import { DeployMethod, GitFormModel } from './types';
export default { export default {
component: GitForm, component: GitForm,
@ -45,7 +45,7 @@ interface Args {
isAdditionalFilesFieldVisible: boolean; isAdditionalFilesFieldVisible: boolean;
isAuthExplanationVisible: boolean; isAuthExplanationVisible: boolean;
isDockerStandalone: boolean; isDockerStandalone: boolean;
deployMethod: 'compose' | 'manifest'; deployMethod: DeployMethod;
isForcePullVisible: boolean; isForcePullVisible: boolean;
} }
@ -73,7 +73,7 @@ export function Primary({
return ( return (
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={() => buildGitValidationSchema([], false)} validationSchema={() => buildGitValidationSchema([], false, 'compose')}
onSubmit={() => {}} onSubmit={() => {}}
> >
{({ values, errors, setValues }) => ( {({ values, errors, setValues }) => (

View File

@ -5,7 +5,7 @@ import { useState } from 'react';
import { ComposePathField } from '@/react/portainer/gitops/ComposePathField'; import { ComposePathField } from '@/react/portainer/gitops/ComposePathField';
import { RefField } from '@/react/portainer/gitops/RefField'; import { RefField } from '@/react/portainer/gitops/RefField';
import { GitFormUrlField } from '@/react/portainer/gitops/GitFormUrlField'; 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 { TimeWindowDisplay } from '@/react/portainer/gitops/TimeWindowDisplay';
import { FormSection } from '@@/form-components/FormSection'; import { FormSection } from '@@/form-components/FormSection';
@ -24,7 +24,7 @@ interface Props {
value: GitFormModel; value: GitFormModel;
onChange: (value: Partial<GitFormModel>) => void; onChange: (value: Partial<GitFormModel>) => void;
environmentType?: 'DOCKER' | 'KUBERNETES' | undefined; environmentType?: 'DOCKER' | 'KUBERNETES' | undefined;
deployMethod?: 'compose' | 'manifest'; deployMethod?: DeployMethod;
isDockerStandalone?: boolean; isDockerStandalone?: boolean;
isAdditionalFilesFieldVisible?: boolean; isAdditionalFilesFieldVisible?: boolean;
isForcePullVisible?: boolean; isForcePullVisible?: boolean;
@ -142,17 +142,24 @@ export function GitForm({
export async function validateGitForm( export async function validateGitForm(
gitCredentials: Array<GitCredential>, gitCredentials: Array<GitCredential>,
formValues: GitFormModel, formValues: GitFormModel,
isCreatedFromCustomTemplate: boolean isCreatedFromCustomTemplate: boolean,
deployMethod: DeployMethod = 'compose'
) { ) {
return validateForm<GitFormModel>( return validateForm<GitFormModel>(
() => buildGitValidationSchema(gitCredentials, isCreatedFromCustomTemplate), () =>
buildGitValidationSchema(
gitCredentials,
isCreatedFromCustomTemplate,
deployMethod
),
formValues formValues
); );
} }
export function buildGitValidationSchema( export function buildGitValidationSchema(
gitCredentials: Array<GitCredential>, gitCredentials: Array<GitCredential>,
isCreatedFromCustomTemplate: boolean isCreatedFromCustomTemplate: boolean,
deployMethod: DeployMethod
): SchemaOf<GitFormModel> { ): SchemaOf<GitFormModel> {
return object({ return object({
RepositoryURL: string() RepositoryURL: string()
@ -171,7 +178,9 @@ export function buildGitValidationSchema(
.required('Repository URL is required'), .required('Repository URL is required'),
RepositoryReferenceName: refFieldValidation(), RepositoryReferenceName: refFieldValidation(),
ComposeFilePathInRepository: string().required( 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([]), AdditionalFiles: array(string().required('Path is required')).default([]),
RepositoryURLValid: boolean().default(false), RepositoryURLValid: boolean().default(false),

View File

@ -52,6 +52,8 @@ export type GitNewCredentialModel = {
export type GitAuthModel = GitCredentialsModel & GitNewCredentialModel; export type GitAuthModel = GitCredentialsModel & GitNewCredentialModel;
export type DeployMethod = 'compose' | 'manifest';
export interface GitFormModel extends GitAuthModel { export interface GitFormModel extends GitAuthModel {
RepositoryURL: string; RepositoryURL: string;
RepositoryURLValid?: boolean; RepositoryURLValid?: boolean;

View File

@ -8,6 +8,7 @@ import { EnvironmentId } from '@/react/portainer/environments/types';
import { useEnvironmentDeploymentOptions } from '@/react/portainer/environments/queries/useEnvironment'; import { useEnvironmentDeploymentOptions } from '@/react/portainer/environments/queries/useEnvironment';
import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment'; import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment';
import { isKubernetesEnvironment } from '@/react/portainer/environments/utils'; import { isKubernetesEnvironment } from '@/react/portainer/environments/utils';
import { DeployMethod } from '@/react/portainer/gitops/types';
import { useInitialValues } from './useInitialValues'; import { useInitialValues } from './useInitialValues';
import { FormValues, initialBuildMethods } from './types'; import { FormValues, initialBuildMethods } from './types';
@ -23,10 +24,12 @@ export function CreateForm({
viewType: 'kube' | 'docker' | 'edge'; viewType: 'kube' | 'docker' | 'edge';
defaultType: StackType; defaultType: StackType;
}) { }) {
const deployMethod: DeployMethod =
defaultType === StackType.Kubernetes ? 'manifest' : 'compose';
const isEdge = !environmentId; const isEdge = !environmentId;
const router = useRouter(); const router = useRouter();
const mutation = useCreateTemplateMutation(); const mutation = useCreateTemplateMutation();
const validation = useValidation({ viewType }); const validation = useValidation({ viewType, deployMethod });
const buildMethods = useBuildMethods(); const buildMethods = useBuildMethods();
const initialValues = useInitialValues({ const initialValues = useInitialValues({

View File

@ -23,6 +23,10 @@ export function useInitialValues({
const { appTemplateId, type = defaultType } = useAppTemplateParams(); 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 { const {
params: { fileContent = '' }, params: { fileContent = '' },
} = useCurrentStateAndParams(); } = useCurrentStateAndParams();
@ -49,7 +53,7 @@ export function useInitialValues({
RepositoryAuthentication: false, RepositoryAuthentication: false,
RepositoryUsername: '', RepositoryUsername: '',
RepositoryPassword: '', RepositoryPassword: '',
ComposeFilePathInRepository: 'docker-compose.yml', ComposeFilePathInRepository: initialFilePathInRepository,
AdditionalFiles: [], AdditionalFiles: [],
RepositoryURLValid: true, RepositoryURLValid: true,
TLSSkipVerify: false, TLSSkipVerify: false,

View File

@ -10,6 +10,7 @@ import { useGitCredentials } from '@/react/portainer/account/git-credentials/git
import { useCurrentUser } from '@/react/hooks/useUser'; import { useCurrentUser } from '@/react/hooks/useUser';
import { useCustomTemplates } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplates'; import { useCustomTemplates } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplates';
import { edgeFieldsetValidation } from '@/react/portainer/templates/custom-templates/CreateView/EdgeSettingsFieldset.validation'; 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 { file } from '@@/form-components/yup-file-validation';
import { import {
@ -22,8 +23,10 @@ import { initialBuildMethods } from './types';
export function useValidation({ export function useValidation({
viewType, viewType,
deployMethod,
}: { }: {
viewType: 'kube' | 'docker' | 'edge'; viewType: 'kube' | 'docker' | 'edge';
deployMethod: DeployMethod;
}) { }) {
const { user } = useCurrentUser(); const { user } = useCurrentUser();
const gitCredentialsQuery = useGitCredentials(user.Id); const gitCredentialsQuery = useGitCredentials(user.Id);
@ -58,7 +61,11 @@ export function useValidation({
Git: mixed().when('Method', { Git: mixed().when('Method', {
is: git.value, is: git.value,
then: () => then: () =>
buildGitValidationSchema(gitCredentialsQuery.data || [], false), buildGitValidationSchema(
gitCredentialsQuery.data || [],
false,
deployMethod
),
}), }),
Variables: variablesValidation(), Variables: variablesValidation(),
EdgeSettings: viewType === 'edge' ? edgeFieldsetValidation() : mixed(), EdgeSettings: viewType === 'edge' ? edgeFieldsetValidation() : mixed(),
@ -68,6 +75,11 @@ export function useValidation({
viewType, viewType,
}) })
), ),
[customTemplatesQuery.data, gitCredentialsQuery.data, viewType] [
customTemplatesQuery.data,
gitCredentialsQuery.data,
viewType,
deployMethod,
]
); );
} }

View File

@ -6,6 +6,8 @@ import { EnvironmentId } from '@/react/portainer/environments/types';
import { useEnvironmentDeploymentOptions } from '@/react/portainer/environments/queries/useEnvironment'; import { useEnvironmentDeploymentOptions } from '@/react/portainer/environments/queries/useEnvironment';
import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment'; import { useCurrentEnvironment } from '@/react/hooks/useCurrentEnvironment';
import { isKubernetesEnvironment } from '@/react/portainer/environments/utils'; 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 { CustomTemplate } from '../types';
import { useUpdateTemplateMutation } from '../queries/useUpdateTemplateMutation'; import { useUpdateTemplateMutation } from '../queries/useUpdateTemplateMutation';
@ -32,10 +34,13 @@ export function EditForm({
const router = useRouter(); const router = useRouter();
const disableEditor = useDisableEditor(isGit); const disableEditor = useDisableEditor(isGit);
const mutation = useUpdateTemplateMutation(); const mutation = useUpdateTemplateMutation();
const deployMethod: DeployMethod =
template.Type === StackType.Kubernetes ? 'manifest' : 'compose';
const validation = useValidation({ const validation = useValidation({
viewType, viewType,
isGit, isGit,
templateId: template.Id, templateId: template.Id,
deployMethod,
}); });
const fileContentQuery = useCustomTemplateFile(template.Id); const fileContentQuery = useCustomTemplateFile(template.Id);

View File

@ -10,6 +10,7 @@ import { useGitCredentials } from '@/react/portainer/account/git-credentials/git
import { useCurrentUser } from '@/react/hooks/useUser'; import { useCurrentUser } from '@/react/hooks/useUser';
import { useCustomTemplates } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplates'; import { useCustomTemplates } from '@/react/portainer/templates/custom-templates/queries/useCustomTemplates';
import { edgeFieldsetValidation } from '@/react/portainer/templates/custom-templates/CreateView/EdgeSettingsFieldset.validation'; import { edgeFieldsetValidation } from '@/react/portainer/templates/custom-templates/CreateView/EdgeSettingsFieldset.validation';
import { DeployMethod } from '@/react/portainer/gitops/types';
import { CustomTemplate } from '../types'; import { CustomTemplate } from '../types';
import { TemplateViewType } from '../useViewType'; import { TemplateViewType } from '../useViewType';
@ -18,10 +19,12 @@ export function useValidation({
isGit, isGit,
templateId, templateId,
viewType, viewType,
deployMethod,
}: { }: {
isGit: boolean; isGit: boolean;
templateId: CustomTemplate['Id']; templateId: CustomTemplate['Id'];
viewType: TemplateViewType; viewType: TemplateViewType;
deployMethod: DeployMethod;
}) { }) {
const { user } = useCurrentUser(); const { user } = useCurrentUser();
const gitCredentialsQuery = useGitCredentials(user.Id); const gitCredentialsQuery = useGitCredentials(user.Id);
@ -47,7 +50,11 @@ export function useValidation({
FileContent: string().required('Template is required.'), FileContent: string().required('Template is required.'),
Git: isGit Git: isGit
? buildGitValidationSchema(gitCredentialsQuery.data || [], false) ? buildGitValidationSchema(
gitCredentialsQuery.data || [],
false,
deployMethod
)
: mixed(), : mixed(),
Variables: variablesValidation(), Variables: variablesValidation(),
EdgeSettings: viewType === 'edge' ? edgeFieldsetValidation() : mixed(), EdgeSettings: viewType === 'edge' ? edgeFieldsetValidation() : mixed(),
@ -64,6 +71,7 @@ export function useValidation({
isGit, isGit,
templateId, templateId,
viewType, viewType,
deployMethod,
] ]
); );
} }