fix(k8s/gitops): missing git auth toggle in k8s app edit page [EE-5320] (#8741)

pull/8755/head
Oscar Zhou 2023-04-10 20:14:13 +12:00 committed by GitHub
parent 5ac1ea3df8
commit a65ffe519a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 93 additions and 29 deletions

View File

@ -151,6 +151,9 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
if payload.RepositoryAuthentication {
password := payload.RepositoryPassword
// When the existing stack is using the custom username/password and the password is not updated,
// the stack should keep using the saved username/password
if password == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
password = stack.GitConfig.Authentication.Password
}

View File

@ -139,6 +139,9 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
repositoryPassword := ""
if payload.RepositoryAuthentication {
repositoryPassword = payload.RepositoryPassword
// When the existing stack is using the custom username/password and the password is not updated,
// the stack should keep using the saved username/password
if repositoryPassword == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
repositoryPassword = stack.GitConfig.Authentication.Password
}

View File

@ -7,6 +7,7 @@ import { GitAuthModel } from '@/react/portainer/gitops/types';
import { gitAuthValidation } from '@/react/portainer/gitops/AuthFieldset';
import { GitCredential } from '@/react/portainer/account/git-credentials/types';
import { getGitCredentials } from '@/react/portainer/account/git-credentials/git-credentials.service';
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
import { validateForm } from '@@/form-components/validate-form';
@ -23,6 +24,8 @@ export default class GitFormAuthFieldsetController {
value?: GitAuthModel;
isAuthEdit: boolean;
onChange?: (value: GitAuthModel) => void;
/* @ngInject */
@ -33,6 +36,7 @@ export default class GitFormAuthFieldsetController {
this.$async = $async;
this.Authentication = Authentication;
this.isAuthEdit = false;
this.handleChange = this.handleChange.bind(this);
this.runGitValidation = this.runGitValidation.bind(this);
}
@ -48,10 +52,10 @@ export default class GitFormAuthFieldsetController {
...newValues,
};
this.onChange?.(value);
await this.runGitValidation(value);
await this.runGitValidation(value, false);
}
async runGitValidation(value: GitAuthModel) {
async runGitValidation(value: GitAuthModel, isAuthEdit: boolean) {
return this.$async(async () => {
this.errors = {};
this.gitFormAuthFieldset?.$setValidity(
@ -61,7 +65,7 @@ export default class GitFormAuthFieldsetController {
);
this.errors = await validateForm<GitAuthModel>(
() => gitAuthValidation(this.gitCredentials),
() => gitAuthValidation(this.gitCredentials, isAuthEdit),
value
);
if (this.errors && Object.keys(this.errors).length > 0) {
@ -75,23 +79,24 @@ export default class GitFormAuthFieldsetController {
}
async $onInit() {
try {
this.gitCredentials = await getGitCredentials(
this.Authentication.getUserDetails().ID
);
} catch (err) {
notifyError(
'Failure',
err as Error,
'Unable to retrieve user saved git credentials'
);
if (isBE) {
try {
// Only BE version support /gitcredentials
this.gitCredentials = await getGitCredentials(
this.Authentication.getUserDetails().ID
);
} catch (err) {
notifyError(
'Failure',
err as Error,
'Unable to retrieve user saved git credentials'
);
}
}
// this should never happen, but just in case
if (!this.value) {
throw new Error('GitFormController: value is required');
}
await this.runGitValidation(this.value);
await this.runGitValidation(this.value, this.isAuthEdit);
}
}

View File

@ -9,13 +9,15 @@ export const gitFormAuthFieldset: IComponentOptions = {
<react-git-form-auth-fieldset
value="$ctrl.value"
on-change="$ctrl.handleChange"
is-explanation-visible="$ctrl.isExplanationVisible"
errors="$ctrl.errors">
is-auth-explanation-visible="$ctrl.isAuthExplanationVisible"
errors="$ctrl.errors"
is-auth-edit="$ctrl.isAuthEdit">
</react-git-form-auth-fieldset>
</ng-form>`,
bindings: {
value: '<',
onChange: '<',
isExplanationVisible: '<',
isAuthExplanationVisible: '<',
isAuthEdit: '<',
},
};

View File

@ -18,6 +18,15 @@ class KubernetesRedeployAppGitFormController {
redeployInProgress: false,
showConfig: false,
isEdit: false,
// isAuthEdit is used to preserve the editing state of the AuthFieldset component.
// Within the stack editing page, users have the option to turn the AuthFieldset on or off
// and save the stack setting. If the user enables the AuthFieldset, it implies that they
// must input new Git authentication, rather than edit existing authentication. Thus,
// a dedicated state tracker is required to differentiate between the editing state of
// AuthFieldset component and the whole stack
// When isAuthEdit is true, PAT field needs to be validated.
isAuthEdit: false,
hasUnsavedChanges: false,
baseWebhookUrl: baseStackWebhookUrl(),
webhookId: createWebhookId(),
@ -121,6 +130,13 @@ class KubernetesRedeployAppGitFormController {
await this.StackService.updateKubeStack({ EndpointId: this.stack.EndpointId, Id: this.stack.Id }, { gitConfig: this.formValues, webhookId: this.state.webhookId });
this.savedFormValues = angular.copy(this.formValues);
this.state.hasUnsavedChanges = false;
if (!(this.stack.GitConfig && this.stack.GitConfig.Authentication)) {
// update the AuthFieldset setting
this.state.isAuthEdit = false;
this.formValues.RepositoryUsername = '';
this.formValues.RepositoryPassword = '';
}
this.Notifications.success('Success', 'Save stack settings successfully');
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to save application settings');
@ -139,14 +155,17 @@ class KubernetesRedeployAppGitFormController {
this.formValues.AutoUpdate = parseAutoUpdateResponse(this.stack.AutoUpdate);
if (this.stack.AutoUpdate.Webhook) {
if (this.stack.AutoUpdate && this.stack.AutoUpdate.Webhook) {
this.state.webhookId = this.stack.AutoUpdate.Webhook;
}
if (this.stack.GitConfig && this.stack.GitConfig.Authentication) {
this.formValues.RepositoryUsername = this.stack.GitConfig.Authentication.Username;
this.formValues.RepositoryPassword = this.stack.GitConfig.Authentication.Password;
this.formValues.RepositoryAuthentication = true;
this.state.isEdit = true;
this.state.isAuthEdit = true;
}
this.savedFormValues = angular.copy(this.formValues);

View File

@ -35,6 +35,14 @@
is-url-valid="true"
></git-form-ref-field>
<git-form-auth-fieldset
ng-if="$ctrl.state.showConfig"
value="$ctrl.formValues"
on-change="($ctrl.onChangeGitAuth)"
is-auth-explanation-visible="true"
is-auth-edit="$ctrl.state.isAuthEdit"
></git-form-auth-fieldset>
<div class="col-sm-12 form-section-title"> Actions </div>
<!-- #Git buttons -->
<button

View File

@ -21,6 +21,15 @@ class StackRedeployGitFormController {
redeployInProgress: false,
showConfig: false,
isEdit: false,
// isAuthEdit is used to preserve the editing state of the AuthFieldset component.
// Within the stack editing page, users have the option to turn the AuthFieldset on or off
// and save the stack setting. If the user enables the AuthFieldset, it implies that they
// must input new Git authentication, rather than edit existing authentication. Thus,
// a dedicated state tracker is required to differentiate between the editing state of
// AuthFieldset component and the whole stack
// When isAuthEdit is true, PAT field needs to be validated.
isAuthEdit: false,
hasUnsavedChanges: false,
baseWebhookUrl: baseStackWebhookUrl(),
webhookId: createWebhookId(),
@ -140,6 +149,12 @@ class StackRedeployGitFormController {
this.state.hasUnsavedChanges = false;
this.Notifications.success('Success', 'Save stack settings successfully');
if (!(this.stack.GitConfig && this.stack.GitConfig.Authentication)) {
// update the AuthFieldset setting
this.state.isAuthEdit = false;
this.formValues.RepositoryUsername = '';
this.formValues.RepositoryPassword = '';
}
this.stack = stack;
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to save stack settings');
@ -188,8 +203,10 @@ class StackRedeployGitFormController {
if (this.stack.GitConfig && this.stack.GitConfig.Authentication) {
this.formValues.RepositoryUsername = this.stack.GitConfig.Authentication.Username;
this.formValues.RepositoryPassword = this.stack.GitConfig.Authentication.Password;
this.formValues.RepositoryAuthentication = true;
this.state.isEdit = true;
this.state.isAuthEdit = true;
}
this.savedFormValues = angular.copy(this.formValues);

View File

@ -37,7 +37,13 @@
is-url-valid="true"
stack-id="$ctrl.gitStackId"
></git-form-ref-field>
<git-form-auth-fieldset ng-if="$ctrl.state.showConfig" value="$ctrl.formValues" on-change="($ctrl.onChangeGitAuth)" is-auth-explanation-visible="true"></git-form-auth-fieldset>
<git-form-auth-fieldset
ng-if="$ctrl.state.showConfig"
value="$ctrl.formValues"
on-change="($ctrl.onChangeGitAuth)"
is-auth-explanation-visible="true"
is-auth-edit="$ctrl.state.isAuthEdit"
></git-form-auth-fieldset>
<environment-variables-panel
ng-model="$ctrl.formValues.Env"

View File

@ -56,7 +56,7 @@ export const gitFormModule = angular
'reactGitFormAuthFieldset',
r2a(withUIRouter(withReactQuery(withCurrentUser(AuthFieldset))), [
'value',
'isExplanationVisible',
'isAuthExplanationVisible',
'onChange',
'errors',
])

View File

@ -18,14 +18,14 @@ import { NewCredentialForm } from './NewCredentialForm';
interface Props {
value: GitAuthModel;
onChange: (value: Partial<GitAuthModel>) => void;
isExplanationVisible?: boolean;
isAuthExplanationVisible?: boolean;
errors?: FormikErrors<GitAuthModel>;
}
export function AuthFieldset({
value,
onChange,
isExplanationVisible,
isAuthExplanationVisible,
errors,
}: Props) {
const [username, setUsername] = useDebounce(
@ -56,7 +56,7 @@ export function AuthFieldset({
{value.RepositoryAuthentication && (
<>
{isExplanationVisible && (
{isAuthExplanationVisible && (
<TextTip color="orange">
Enabling authentication will store the credentials and it is
advisable to use a git service account
@ -143,7 +143,8 @@ export function AuthFieldset({
}
export function gitAuthValidation(
gitCredentials: Array<GitCredential>
gitCredentials: Array<GitCredential>,
isAuthEdit: boolean
): SchemaOf<GitAuthModel> {
return object({
RepositoryAuthentication: boolean().default(false),
@ -156,7 +157,7 @@ export function gitAuthValidation(
.default(''),
RepositoryPassword: string()
.when(['RepositoryAuthentication', 'RepositoryGitCredentialID'], {
is: (auth: boolean, id: number) => auth && !id,
is: (auth: boolean, id: number) => auth && !id && !isAuthEdit,
then: string().required('Password is required'),
})
.default(''),

View File

@ -51,7 +51,7 @@ export function GitForm({
<AuthFieldset
value={value}
onChange={handleChange}
isExplanationVisible={isAuthExplanationVisible}
isAuthExplanationVisible={isAuthExplanationVisible}
errors={errors}
/>
@ -162,5 +162,5 @@ export function buildGitValidationSchema(
RepositoryURLValid: boolean().default(false),
AutoUpdate: autoUpdateValidation().nullable(),
TLSSkipVerify: boolean().default(false),
}).concat(gitAuthValidation(gitCredentials));
}).concat(gitAuthValidation(gitCredentials, false));
}