mirror of https://github.com/portainer/portainer
fix(k8s/gitops): missing git auth toggle in k8s app edit page [EE-5320] (#8741)
parent
5ac1ea3df8
commit
a65ffe519a
|
@ -151,6 +151,9 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
|
||||||
|
|
||||||
if payload.RepositoryAuthentication {
|
if payload.RepositoryAuthentication {
|
||||||
password := payload.RepositoryPassword
|
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 {
|
if password == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
|
||||||
password = stack.GitConfig.Authentication.Password
|
password = stack.GitConfig.Authentication.Password
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,6 +139,9 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
||||||
repositoryPassword := ""
|
repositoryPassword := ""
|
||||||
if payload.RepositoryAuthentication {
|
if payload.RepositoryAuthentication {
|
||||||
repositoryPassword = payload.RepositoryPassword
|
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 {
|
if repositoryPassword == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
|
||||||
repositoryPassword = stack.GitConfig.Authentication.Password
|
repositoryPassword = stack.GitConfig.Authentication.Password
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { GitAuthModel } from '@/react/portainer/gitops/types';
|
||||||
import { gitAuthValidation } from '@/react/portainer/gitops/AuthFieldset';
|
import { gitAuthValidation } from '@/react/portainer/gitops/AuthFieldset';
|
||||||
import { GitCredential } from '@/react/portainer/account/git-credentials/types';
|
import { GitCredential } from '@/react/portainer/account/git-credentials/types';
|
||||||
import { getGitCredentials } from '@/react/portainer/account/git-credentials/git-credentials.service';
|
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';
|
import { validateForm } from '@@/form-components/validate-form';
|
||||||
|
|
||||||
|
@ -23,6 +24,8 @@ export default class GitFormAuthFieldsetController {
|
||||||
|
|
||||||
value?: GitAuthModel;
|
value?: GitAuthModel;
|
||||||
|
|
||||||
|
isAuthEdit: boolean;
|
||||||
|
|
||||||
onChange?: (value: GitAuthModel) => void;
|
onChange?: (value: GitAuthModel) => void;
|
||||||
|
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
|
@ -33,6 +36,7 @@ export default class GitFormAuthFieldsetController {
|
||||||
this.$async = $async;
|
this.$async = $async;
|
||||||
this.Authentication = Authentication;
|
this.Authentication = Authentication;
|
||||||
|
|
||||||
|
this.isAuthEdit = false;
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.handleChange = this.handleChange.bind(this);
|
||||||
this.runGitValidation = this.runGitValidation.bind(this);
|
this.runGitValidation = this.runGitValidation.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -48,10 +52,10 @@ export default class GitFormAuthFieldsetController {
|
||||||
...newValues,
|
...newValues,
|
||||||
};
|
};
|
||||||
this.onChange?.(value);
|
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 () => {
|
return this.$async(async () => {
|
||||||
this.errors = {};
|
this.errors = {};
|
||||||
this.gitFormAuthFieldset?.$setValidity(
|
this.gitFormAuthFieldset?.$setValidity(
|
||||||
|
@ -61,7 +65,7 @@ export default class GitFormAuthFieldsetController {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.errors = await validateForm<GitAuthModel>(
|
this.errors = await validateForm<GitAuthModel>(
|
||||||
() => gitAuthValidation(this.gitCredentials),
|
() => gitAuthValidation(this.gitCredentials, isAuthEdit),
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
if (this.errors && Object.keys(this.errors).length > 0) {
|
if (this.errors && Object.keys(this.errors).length > 0) {
|
||||||
|
@ -75,23 +79,24 @@ export default class GitFormAuthFieldsetController {
|
||||||
}
|
}
|
||||||
|
|
||||||
async $onInit() {
|
async $onInit() {
|
||||||
try {
|
if (isBE) {
|
||||||
this.gitCredentials = await getGitCredentials(
|
try {
|
||||||
this.Authentication.getUserDetails().ID
|
// Only BE version support /gitcredentials
|
||||||
);
|
this.gitCredentials = await getGitCredentials(
|
||||||
} catch (err) {
|
this.Authentication.getUserDetails().ID
|
||||||
notifyError(
|
);
|
||||||
'Failure',
|
} catch (err) {
|
||||||
err as Error,
|
notifyError(
|
||||||
'Unable to retrieve user saved git credentials'
|
'Failure',
|
||||||
);
|
err as Error,
|
||||||
|
'Unable to retrieve user saved git credentials'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this should never happen, but just in case
|
// this should never happen, but just in case
|
||||||
if (!this.value) {
|
if (!this.value) {
|
||||||
throw new Error('GitFormController: value is required');
|
throw new Error('GitFormController: value is required');
|
||||||
}
|
}
|
||||||
|
await this.runGitValidation(this.value, this.isAuthEdit);
|
||||||
await this.runGitValidation(this.value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,15 @@ export const gitFormAuthFieldset: IComponentOptions = {
|
||||||
<react-git-form-auth-fieldset
|
<react-git-form-auth-fieldset
|
||||||
value="$ctrl.value"
|
value="$ctrl.value"
|
||||||
on-change="$ctrl.handleChange"
|
on-change="$ctrl.handleChange"
|
||||||
is-explanation-visible="$ctrl.isExplanationVisible"
|
is-auth-explanation-visible="$ctrl.isAuthExplanationVisible"
|
||||||
errors="$ctrl.errors">
|
errors="$ctrl.errors"
|
||||||
|
is-auth-edit="$ctrl.isAuthEdit">
|
||||||
</react-git-form-auth-fieldset>
|
</react-git-form-auth-fieldset>
|
||||||
</ng-form>`,
|
</ng-form>`,
|
||||||
bindings: {
|
bindings: {
|
||||||
value: '<',
|
value: '<',
|
||||||
onChange: '<',
|
onChange: '<',
|
||||||
isExplanationVisible: '<',
|
isAuthExplanationVisible: '<',
|
||||||
|
isAuthEdit: '<',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,15 @@ class KubernetesRedeployAppGitFormController {
|
||||||
redeployInProgress: false,
|
redeployInProgress: false,
|
||||||
showConfig: false,
|
showConfig: false,
|
||||||
isEdit: 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,
|
hasUnsavedChanges: false,
|
||||||
baseWebhookUrl: baseStackWebhookUrl(),
|
baseWebhookUrl: baseStackWebhookUrl(),
|
||||||
webhookId: createWebhookId(),
|
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 });
|
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.savedFormValues = angular.copy(this.formValues);
|
||||||
this.state.hasUnsavedChanges = false;
|
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');
|
this.Notifications.success('Success', 'Save stack settings successfully');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to save application settings');
|
this.Notifications.error('Failure', err, 'Unable to save application settings');
|
||||||
|
@ -139,14 +155,17 @@ class KubernetesRedeployAppGitFormController {
|
||||||
|
|
||||||
this.formValues.AutoUpdate = parseAutoUpdateResponse(this.stack.AutoUpdate);
|
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;
|
this.state.webhookId = this.stack.AutoUpdate.Webhook;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stack.GitConfig && this.stack.GitConfig.Authentication) {
|
if (this.stack.GitConfig && this.stack.GitConfig.Authentication) {
|
||||||
this.formValues.RepositoryUsername = this.stack.GitConfig.Authentication.Username;
|
this.formValues.RepositoryUsername = this.stack.GitConfig.Authentication.Username;
|
||||||
|
this.formValues.RepositoryPassword = this.stack.GitConfig.Authentication.Password;
|
||||||
|
|
||||||
this.formValues.RepositoryAuthentication = true;
|
this.formValues.RepositoryAuthentication = true;
|
||||||
this.state.isEdit = true;
|
this.state.isEdit = true;
|
||||||
|
this.state.isAuthEdit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.savedFormValues = angular.copy(this.formValues);
|
this.savedFormValues = angular.copy(this.formValues);
|
||||||
|
|
|
@ -35,6 +35,14 @@
|
||||||
is-url-valid="true"
|
is-url-valid="true"
|
||||||
></git-form-ref-field>
|
></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>
|
<div class="col-sm-12 form-section-title"> Actions </div>
|
||||||
<!-- #Git buttons -->
|
<!-- #Git buttons -->
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -21,6 +21,15 @@ class StackRedeployGitFormController {
|
||||||
redeployInProgress: false,
|
redeployInProgress: false,
|
||||||
showConfig: false,
|
showConfig: false,
|
||||||
isEdit: 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,
|
hasUnsavedChanges: false,
|
||||||
baseWebhookUrl: baseStackWebhookUrl(),
|
baseWebhookUrl: baseStackWebhookUrl(),
|
||||||
webhookId: createWebhookId(),
|
webhookId: createWebhookId(),
|
||||||
|
@ -140,6 +149,12 @@ class StackRedeployGitFormController {
|
||||||
this.state.hasUnsavedChanges = false;
|
this.state.hasUnsavedChanges = false;
|
||||||
this.Notifications.success('Success', 'Save stack settings successfully');
|
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;
|
this.stack = stack;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to save stack settings');
|
this.Notifications.error('Failure', err, 'Unable to save stack settings');
|
||||||
|
@ -188,8 +203,10 @@ class StackRedeployGitFormController {
|
||||||
|
|
||||||
if (this.stack.GitConfig && this.stack.GitConfig.Authentication) {
|
if (this.stack.GitConfig && this.stack.GitConfig.Authentication) {
|
||||||
this.formValues.RepositoryUsername = this.stack.GitConfig.Authentication.Username;
|
this.formValues.RepositoryUsername = this.stack.GitConfig.Authentication.Username;
|
||||||
|
this.formValues.RepositoryPassword = this.stack.GitConfig.Authentication.Password;
|
||||||
this.formValues.RepositoryAuthentication = true;
|
this.formValues.RepositoryAuthentication = true;
|
||||||
this.state.isEdit = true;
|
this.state.isEdit = true;
|
||||||
|
this.state.isAuthEdit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.savedFormValues = angular.copy(this.formValues);
|
this.savedFormValues = angular.copy(this.formValues);
|
||||||
|
|
|
@ -37,7 +37,13 @@
|
||||||
is-url-valid="true"
|
is-url-valid="true"
|
||||||
stack-id="$ctrl.gitStackId"
|
stack-id="$ctrl.gitStackId"
|
||||||
></git-form-ref-field>
|
></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
|
<environment-variables-panel
|
||||||
ng-model="$ctrl.formValues.Env"
|
ng-model="$ctrl.formValues.Env"
|
||||||
|
|
|
@ -56,7 +56,7 @@ export const gitFormModule = angular
|
||||||
'reactGitFormAuthFieldset',
|
'reactGitFormAuthFieldset',
|
||||||
r2a(withUIRouter(withReactQuery(withCurrentUser(AuthFieldset))), [
|
r2a(withUIRouter(withReactQuery(withCurrentUser(AuthFieldset))), [
|
||||||
'value',
|
'value',
|
||||||
'isExplanationVisible',
|
'isAuthExplanationVisible',
|
||||||
'onChange',
|
'onChange',
|
||||||
'errors',
|
'errors',
|
||||||
])
|
])
|
||||||
|
|
|
@ -18,14 +18,14 @@ import { NewCredentialForm } from './NewCredentialForm';
|
||||||
interface Props {
|
interface Props {
|
||||||
value: GitAuthModel;
|
value: GitAuthModel;
|
||||||
onChange: (value: Partial<GitAuthModel>) => void;
|
onChange: (value: Partial<GitAuthModel>) => void;
|
||||||
isExplanationVisible?: boolean;
|
isAuthExplanationVisible?: boolean;
|
||||||
errors?: FormikErrors<GitAuthModel>;
|
errors?: FormikErrors<GitAuthModel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AuthFieldset({
|
export function AuthFieldset({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
isExplanationVisible,
|
isAuthExplanationVisible,
|
||||||
errors,
|
errors,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [username, setUsername] = useDebounce(
|
const [username, setUsername] = useDebounce(
|
||||||
|
@ -56,7 +56,7 @@ export function AuthFieldset({
|
||||||
|
|
||||||
{value.RepositoryAuthentication && (
|
{value.RepositoryAuthentication && (
|
||||||
<>
|
<>
|
||||||
{isExplanationVisible && (
|
{isAuthExplanationVisible && (
|
||||||
<TextTip color="orange">
|
<TextTip color="orange">
|
||||||
Enabling authentication will store the credentials and it is
|
Enabling authentication will store the credentials and it is
|
||||||
advisable to use a git service account
|
advisable to use a git service account
|
||||||
|
@ -143,7 +143,8 @@ export function AuthFieldset({
|
||||||
}
|
}
|
||||||
|
|
||||||
export function gitAuthValidation(
|
export function gitAuthValidation(
|
||||||
gitCredentials: Array<GitCredential>
|
gitCredentials: Array<GitCredential>,
|
||||||
|
isAuthEdit: boolean
|
||||||
): SchemaOf<GitAuthModel> {
|
): SchemaOf<GitAuthModel> {
|
||||||
return object({
|
return object({
|
||||||
RepositoryAuthentication: boolean().default(false),
|
RepositoryAuthentication: boolean().default(false),
|
||||||
|
@ -156,7 +157,7 @@ export function gitAuthValidation(
|
||||||
.default(''),
|
.default(''),
|
||||||
RepositoryPassword: string()
|
RepositoryPassword: string()
|
||||||
.when(['RepositoryAuthentication', 'RepositoryGitCredentialID'], {
|
.when(['RepositoryAuthentication', 'RepositoryGitCredentialID'], {
|
||||||
is: (auth: boolean, id: number) => auth && !id,
|
is: (auth: boolean, id: number) => auth && !id && !isAuthEdit,
|
||||||
then: string().required('Password is required'),
|
then: string().required('Password is required'),
|
||||||
})
|
})
|
||||||
.default(''),
|
.default(''),
|
||||||
|
|
|
@ -51,7 +51,7 @@ export function GitForm({
|
||||||
<AuthFieldset
|
<AuthFieldset
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
isExplanationVisible={isAuthExplanationVisible}
|
isAuthExplanationVisible={isAuthExplanationVisible}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -162,5 +162,5 @@ export function buildGitValidationSchema(
|
||||||
RepositoryURLValid: boolean().default(false),
|
RepositoryURLValid: boolean().default(false),
|
||||||
AutoUpdate: autoUpdateValidation().nullable(),
|
AutoUpdate: autoUpdateValidation().nullable(),
|
||||||
TLSSkipVerify: boolean().default(false),
|
TLSSkipVerify: boolean().default(false),
|
||||||
}).concat(gitAuthValidation(gitCredentials));
|
}).concat(gitAuthValidation(gitCredentials, false));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue