From 6dc7405cf977e29334f4d0c04e9d2023c04d119d Mon Sep 17 00:00:00 2001 From: Keith Chong Date: Thu, 5 Sep 2024 05:37:59 -0400 Subject: [PATCH 1/8] fix: Delete button should be disabled when one source remains (#18804) --- .../application-details.tsx | 1 + .../application-parameters-source.tsx | 4 + .../application-parameters.scss | 4 + .../application-parameters.tsx | 134 +++++-- .../application-parameters/source-panel.scss | 18 + .../application-parameters/source-panel.tsx | 375 ++++++++++++++++++ .../resource-details/resource-details.tsx | 4 +- ui/src/app/applications/components/utils.tsx | 42 +- .../editable-panel/editable-section.tsx | 40 +- 9 files changed, 586 insertions(+), 36 deletions(-) create mode 100644 ui/src/app/applications/components/application-parameters/source-panel.scss create mode 100644 ui/src/app/applications/components/application-parameters/source-panel.tsx diff --git a/ui/src/app/applications/components/application-details/application-details.tsx b/ui/src/app/applications/components/application-details/application-details.tsx index 050ed358a5e20..8e2d968727a8a 100644 --- a/ui/src/app/applications/components/application-details/application-details.tsx +++ b/ui/src/app/applications/components/application-details/application-details.tsx @@ -842,6 +842,7 @@ export class ApplicationDetails extends React.Component this.updateApp(app, query)} selectedNode={selectedNode} + appCxt={this.context} tab={tab} /> diff --git a/ui/src/app/applications/components/application-parameters/application-parameters-source.tsx b/ui/src/app/applications/components/application-parameters/application-parameters-source.tsx index 4acbcdd82fcf6..2d494af941d3d 100644 --- a/ui/src/app/applications/components/application-parameters/application-parameters-source.tsx +++ b/ui/src/app/applications/components/application-parameters/application-parameters-source.tsx @@ -24,8 +24,10 @@ export interface ApplicationParametersPanelProps { viewBottom?: string | React.ReactNode; editTop?: (formApi: FormApi) => React.ReactNode; editBottom?: (formApi: FormApi) => React.ReactNode; + numberOfSources?: number; noReadonlyMode?: boolean; collapsible?: boolean; + deleteSource: () => void; } interface ApplicationParametersPanelState { @@ -64,9 +66,11 @@ export class ApplicationParametersSource extends React.Component { this.setState({editBottom: editClicked}); }} + deleteSource={this.props.deleteSource} /> {this.props.itemsTop && ( diff --git a/ui/src/app/applications/components/application-parameters/application-parameters.scss b/ui/src/app/applications/components/application-parameters/application-parameters.scss index 86a3af27e24f5..d40c88ac05340 100644 --- a/ui/src/app/applications/components/application-parameters/application-parameters.scss +++ b/ui/src/app/applications/components/application-parameters/application-parameters.scss @@ -42,6 +42,10 @@ right: 1em; } + .source-panel-buttons { + margin-bottom: 10px; + } + .argo-field { line-height: 1.15; } diff --git a/ui/src/app/applications/components/application-parameters/application-parameters.tsx b/ui/src/app/applications/components/application-parameters/application-parameters.tsx index 427be982b05f6..ec6d67dae46a1 100644 --- a/ui/src/app/applications/components/application-parameters/application-parameters.tsx +++ b/ui/src/app/applications/components/application-parameters/application-parameters.tsx @@ -1,4 +1,4 @@ -import {AutocompleteField, DataLoader, FormField, FormSelect, getNestedField} from 'argo-ui'; +import {AutocompleteField, DataLoader, ErrorNotification, FormField, FormSelect, getNestedField, NotificationType, SlidingPanel} from 'argo-ui'; import * as React from 'react'; import {FieldApi, FormApi, FormField as ReactFormField, Text, TextArea} from 'react-form'; import {cloneDeep} from 'lodash-es'; @@ -18,7 +18,8 @@ import { Revision, Repo, EditablePanel, - EditablePanelItem + EditablePanelItem, + Spinner } from '../../../shared/components'; import * as models from '../../../shared/models'; import {ApplicationSourceDirectory, Plugin} from '../../../shared/models'; @@ -27,13 +28,15 @@ import {ImageTagFieldEditor} from './kustomize'; import * as kustomize from './kustomize-image'; import {VarsInputField} from './vars-input-field'; import {concatMaps} from '../../../shared/utils'; -import {getAppDefaultSource} from '../utils'; +import {deleteSourceAction, getAppDefaultSource, helpTip} from '../utils'; import * as jsYaml from 'js-yaml'; import {RevisionFormField} from '../revision-form-field/revision-form-field'; import classNames from 'classnames'; import {ApplicationParametersSource} from './application-parameters-source'; import './application-parameters.scss'; +import {AppContext} from '../../../shared/context'; +import {SourcePanel} from './source-panel'; const TextWithMetadataField = ReactFormField((props: {metadata: {value: string}; fieldApi: FieldApi; className: string}) => { const { @@ -148,17 +151,30 @@ export const ApplicationParameters = (props: { setPageNumber?: (x: number) => any; collapsedSources?: boolean[]; handleCollapse?: (i: number, isCollapsed: boolean) => void; + appContext?: AppContext; + tempSource?: models.ApplicationSource; }) => { const app = cloneDeep(props.application); const source = getAppDefaultSource(app); // For source field const appSources = app?.spec.sources; const [removedOverrides, setRemovedOverrides] = React.useState(new Array()); const collapsible = props.collapsedSources !== undefined && props.handleCollapse !== undefined; + const [createApi, setCreateApi] = React.useState(null); + const [isAddingSource, setIsAddingSource] = React.useState(false); + const [isSavingSource, setIsSavingSource] = React.useState(false); const [appParamsDeletedState, setAppParamsDeletedState] = React.useState([]); if (app.spec.sources?.length > 0 && !props.details) { + // For multi-source case only return (
+
+ +
+ setIsAddingSource(false)} + header={ +
+ {' '} + +
+ }> + { + setCreateApi(api); + }} + onSubmitFailure={errors => { + props.appContext.apis.notifications.show({ + content: 'Cannot add source: ' + errors.toString(), + type: NotificationType.Warning + }); + }} + updateApp={async updatedAppSource => { + setIsSavingSource(true); + props.application.spec.sources.push(updatedAppSource.spec.source); + try { + await services.applications.update(props.application); + setIsAddingSource(false); + } catch (e) { + props.application.spec.sources.pop(); + props.appContext.apis.notifications.show({ + content: , + type: NotificationType.Error + }); + } finally { + setIsSavingSource(false); + } + }} + /> +
); } else { - // For the other old/existings references of ApplicationParameters that have details already loaded. They are single source + // For the three other references of ApplicationParameters. They are single source. + // Create App, Add source, Rollback and History let attributes: EditablePanelItem[] = []; if (props.details) { return getEditablePanel( - gatherDetails(0, props.details, attributes, source, app, setRemovedOverrides, removedOverrides, appParamsDeletedState, setAppParamsDeletedState, false), + gatherDetails( + 0, + props.details, + attributes, + props.tempSource ? props.tempSource : source, + app, + setRemovedOverrides, + removedOverrides, + appParamsDeletedState, + setAppParamsDeletedState, + false + ), props.details ); } else { - // For single source field, for resource details where we have to do the load. + // For single source field, details page where we have to do the load to retrieve repo details return ( getSingleSource(application)}> {(details: models.RepoAppDetails) => { @@ -247,7 +328,10 @@ export const ApplicationParameters = (props: { )} - getSourceFromAppSources(src, app.metadata.name, app.spec.project, index, 0)}> + getSourceFromAppSources(src, app.metadata.name, app.spec.project, index, 0)}> {(details: models.RepoAppDetails) => getEditablePanelForOneSource(details, index, app.spec.sources[index])} @@ -270,10 +354,10 @@ export const ApplicationParameters = (props: { function isDefinedWithVersion(item: any) { return item !== null && item !== undefined && item.match(/:/); } - if (updatedSrc.helm && updatedSrc.helm.parameters) { + if (updatedSrc && updatedSrc.helm?.parameters) { updatedSrc.helm.parameters = updatedSrc.helm.parameters.filter(isDefined); } - if (updatedSrc.kustomize && updatedSrc.kustomize.images) { + if (updatedSrc && updatedSrc.kustomize?.images) { updatedSrc.kustomize.images = updatedSrc.kustomize.images.filter(isDefinedWithVersion); } @@ -295,7 +379,7 @@ export const ApplicationParameters = (props: { params = params.filter(param => !appParamsDeletedState.includes(param.name)); input.spec.source.plugin.parameters = params; } - if (input.spec.source.helm && input.spec.source.helm.valuesObject) { + if (input.spec.source && input.spec.source.helm?.valuesObject) { input.spec.source.helm.valuesObject = jsYaml.load(input.spec.source.helm.values); // Deserialize json input.spec.source.helm.values = ''; } @@ -303,7 +387,7 @@ export const ApplicationParameters = (props: { setRemovedOverrides(new Array()); }) } - values={((repoAppDetails.plugin || app?.spec?.source?.plugin) && cloneDeep(app)) || app} + values={((repoAppDetails?.plugin || app?.spec?.source?.plugin) && cloneDeep(app)) || app} validate={updatedApp => { const errors = {} as any; @@ -312,7 +396,7 @@ export const ApplicationParameters = (props: { errors[fieldPath] = invalid.length > 0 ? 'All fields must have name' : null; } - if (updatedApp.spec.source.helm && updatedApp.spec.source.helm.values) { + if (updatedApp.spec.source && updatedApp.spec.source.helm?.values) { const parsedValues = jsYaml.load(updatedApp.spec.source.helm.values); errors['spec.source.helm.values'] = typeof parsedValues === 'object' ? null : 'Values must be a map'; } @@ -320,12 +404,12 @@ export const ApplicationParameters = (props: { return errors; }} onModeSwitch={ - repoAppDetails.plugin && + repoAppDetails?.plugin && (() => { setAppParamsDeletedState([]); }) } - title={repoAppDetails.type.toLocaleUpperCase()} + title={repoAppDetails?.type?.toLocaleUpperCase()} items={items as EditablePanelItem[]} noReadonlyMode={props.noReadonlyMode} hasMultipleSources={false} @@ -402,7 +486,7 @@ export const ApplicationParameters = (props: { saveBottom={ props.save && (async (input: models.Application) => { - const updatedSrc = input.spec.sources[ind]; + const appSrc = input.spec.sources[ind]; function isDefined(item: any) { return item !== null && item !== undefined; @@ -411,11 +495,11 @@ export const ApplicationParameters = (props: { return item !== null && item !== undefined && item.match(/:/); } - if (updatedSrc.helm && updatedSrc.helm.parameters) { - updatedSrc.helm.parameters = updatedSrc.helm.parameters.filter(isDefined); + if (appSrc.helm && appSrc.helm.parameters) { + appSrc.helm.parameters = appSrc.helm.parameters.filter(isDefined); } - if (updatedSrc.kustomize && updatedSrc.kustomize.images) { - updatedSrc.kustomize.images = updatedSrc.kustomize.images.filter(isDefinedWithVersion); + if (appSrc.kustomize && appSrc.kustomize.images) { + appSrc.kustomize.images = appSrc.kustomize.images.filter(isDefinedWithVersion); } let params = input.spec?.sources[ind]?.plugin?.parameters; @@ -435,11 +519,11 @@ export const ApplicationParameters = (props: { } params = params.filter(param => !appParamsDeletedState.includes(param.name)); - updatedSrc.plugin.parameters = params; + appSrc.plugin.parameters = params; } - if (updatedSrc.helm && updatedSrc.helm.valuesObject) { - updatedSrc.helm.valuesObject = jsYaml.load(updatedSrc.helm.values); // Deserialize json - updatedSrc.helm.values = ''; + if (appSrc.helm && appSrc.helm.valuesObject) { + appSrc.helm.valuesObject = jsYaml.load(appSrc.helm.values); // Deserialize json + appSrc.helm.values = ''; } await props.save(input, {}); @@ -486,6 +570,10 @@ export const ApplicationParameters = (props: { itemsTop={upperPanel as EditablePanelItem[]} noReadonlyMode={props.noReadonlyMode} collapsible={collapsible} + numberOfSources={app?.spec?.sources.length} + deleteSource={() => { + deleteSourceAction(app, app.spec.sources.at(ind), props.appContext); + }} /> ); } diff --git a/ui/src/app/applications/components/application-parameters/source-panel.scss b/ui/src/app/applications/components/application-parameters/source-panel.scss new file mode 100644 index 0000000000000..9ee0b7c0aa785 --- /dev/null +++ b/ui/src/app/applications/components/application-parameters/source-panel.scss @@ -0,0 +1,18 @@ +@import 'node_modules/argo-ui/src/styles/config'; + +.new-source-panel { + + .checkbox-container { + margin: 0.5em ; + } + + pre { + font-family: monospace; + line-height: normal; + white-space: pre; + } + + .row.argo-form-row .columns { + padding-left: 0; + } +} diff --git a/ui/src/app/applications/components/application-parameters/source-panel.tsx b/ui/src/app/applications/components/application-parameters/source-panel.tsx new file mode 100644 index 0000000000000..8e750b6e4a9b9 --- /dev/null +++ b/ui/src/app/applications/components/application-parameters/source-panel.tsx @@ -0,0 +1,375 @@ +import {AutocompleteField, DataLoader, DropDownMenu, FormField} from 'argo-ui'; +import * as deepMerge from 'deepmerge'; +import * as React from 'react'; +import {Form, FormApi, FormErrors, Text} from 'react-form'; +import {ApplicationParameters} from '../../../applications/components/application-parameters/application-parameters'; +import {RevisionFormField} from '../../../applications/components/revision-form-field/revision-form-field'; +import {RevisionHelpIcon} from '../../../shared/components'; +import * as models from '../../../shared/models'; +import {services} from '../../../shared/services'; +import './source-panel.scss'; + +// This is similar to what is in application-create-panel.tsx. If the create panel +// is modified to support multi-source apps, then we should refactor and common these up +const appTypes = new Array<{field: string; type: models.AppSourceType}>( + {type: 'Helm', field: 'helm'}, + {type: 'Kustomize', field: 'kustomize'}, + {type: 'Directory', field: 'directory'}, + {type: 'Plugin', field: 'plugin'} +); + +// This is similar to the same function in application-create-panel.tsx. If the create panel +// is modified to support multi-source apps, then we should refactor and common these up +function normalizeAppSource(app: models.Application, type: string): boolean { + const source = app.spec.source; + // eslint-disable-next-line no-prototype-builtins + const repoType = (source.hasOwnProperty('chart') && 'helm') || 'git'; + if (repoType !== type) { + if (type === 'git') { + source.path = source.chart; + delete source.chart; + source.targetRevision = 'HEAD'; + } else { + source.chart = source.path; + delete source.path; + source.targetRevision = ''; + } + return true; + } + return false; +} + +// Use a single source app to represent the 'new source'. This panel will make use of the source field only. +// However, we need to use a template based on an Application so that we can reuse the application-parameters code +const DEFAULT_APP: Partial = { + apiVersion: 'argoproj.io/v1alpha1', + kind: 'Application', + metadata: { + name: '' + }, + spec: { + destination: { + name: '', + namespace: '', + server: '' + }, + source: { + path: '', + repoURL: '', + ref: '', + targetRevision: 'HEAD' + }, + sources: [], + project: '' + } +}; + +export const SourcePanel = (props: { + appCurrent: models.Application; + onSubmitFailure: (error: string) => any; + updateApp: (app: models.Application) => any; + getFormApi: (api: FormApi) => any; +}) => { + const [explicitPathType, setExplicitPathType] = React.useState<{path: string; type: models.AppSourceType}>(null); + const appInEdit = deepMerge(DEFAULT_APP, {}); + + function normalizeTypeFields(formApi: FormApi, type: models.AppSourceType) { + const appToNormalize = formApi.getFormState().values; + for (const item of appTypes) { + if (item.type !== type) { + delete appToNormalize.spec.source[item.field]; + } + } + formApi.setAllValues(appToNormalize); + } + + return ( + + Promise.all([services.repos.list()]).then(([reposInfo]) => ({reposInfo}))}> + {({reposInfo}) => { + const repos = reposInfo.map(info => info.repo).sort(); + return ( +
+
{ + let samePath = false; + let sameChartVersion = false; + let pathError = null; + let chartError = null; + if (a.spec.source.repoURL && a.spec.source.path) { + props.appCurrent.spec.sources.forEach(source => { + if (source.repoURL === a.spec.source.repoURL && source.path === a.spec.source.path) { + samePath = true; + pathError = 'Provided path in the selected repository URL was already added to this multi-source application'; + } + }); + } + if (a.spec.source.repoURL && a.spec.source.chart) { + props.appCurrent.spec.sources.forEach(source => { + if ( + source.repoURL === a.spec.source.repoURL && + source.chart === a.spec.source.chart && + source.targetRevision === a.spec.source.targetRevision + ) { + sameChartVersion = true; + chartError = + 'Version ' + + source.targetRevision + + ' of chart ' + + source.chart + + ' from the selected repository was already added to this multi-source application'; + } + }); + } + if (!samePath) { + if (!a.spec.source.path && !a.spec.source.chart && !a.spec.source.ref) { + pathError = 'Path or Ref is required'; + } + } + if (!sameChartVersion) { + if (!a.spec.source.chart && !a.spec.source.path && !a.spec.source.ref) { + chartError = 'Chart is required'; + } + } + return { + 'spec.source.repoURL': !a.spec.source.repoURL && 'Repository URL is required', + // eslint-disable-next-line no-prototype-builtins + 'spec.source.targetRevision': !a.spec.source.targetRevision && a.spec.source.hasOwnProperty('chart') && 'Version is required', + 'spec.source.path': pathError, + 'spec.source.chart': chartError + }; + }} + defaultValues={appInEdit} + onSubmitFailure={(errors: FormErrors) => { + let errorString: string = ''; + let i = 0; + for (const key in errors) { + if (errors[key]) { + i++; + errorString = errorString.concat(i + '. ' + errors[key] + ' '); + } + } + props.onSubmitFailure(errorString); + }} + onSubmit={values => { + props.updateApp(values as models.Application); + }} + getApi={props.getFormApi}> + {api => { + // eslint-disable-next-line no-prototype-builtins + const repoType = (api.getFormState().values.spec.source.hasOwnProperty('chart') && 'helm') || 'git'; + const repoInfo = reposInfo.find(info => info.repo === api.getFormState().values.spec.source.repoURL); + if (repoInfo) { + normalizeAppSource(appInEdit, repoInfo.type || 'git'); + } + const sourcePanel = () => ( +
+

SOURCE

+
+
+ +
+
+
+ {(repoInfo && ( + + {(repoInfo.type || 'git').toUpperCase()} + + )) || ( + ( +

+ {repoType.toUpperCase()} +

+ )} + items={['git', 'helm'].map((type: 'git' | 'helm') => ({ + title: type.toUpperCase(), + action: () => { + if (repoType !== type) { + const updatedApp = api.getFormState().values as models.Application; + if (normalizeAppSource(updatedApp, type)) { + api.setAllValues(updatedApp); + } + } + } + }))} + /> + )} +
+
+
+ {(repoType === 'git' && ( + + +
+ + (src.repoURL && + (await services.repos + .apps(src.repoURL, src.revision, appInEdit.metadata.name, props.appCurrent.spec.project) + .then(apps => Array.from(new Set(apps.map(item => item.path))).sort()) + .catch(() => new Array()))) || + new Array() + }> + {(apps: string[]) => ( + + )} + +
+
+ +
+
+ )) || ( + + (src.repoURL && services.repos.charts(src.repoURL).catch(() => new Array())) || + new Array() + }> + {(charts: models.HelmChart[]) => { + const selectedChart = charts.find(chart => chart.name === api.getFormState().values.spec.source.chart); + return ( +
+
+ chart.name), + filterSuggestions: true + }} + /> +
+
+ + +
+
+ ); + }} +
+ )} +
+ ); + + const typePanel = () => ( + { + if (src.repoURL && src.targetRevision && (src.path || src.chart)) { + return services.repos.appDetails(src, src.appName, props.appCurrent.spec.project, 0, 0).catch(() => ({ + type: 'Directory', + details: {} + })); + } else { + return { + type: 'Directory', + details: {} + }; + } + }}> + {(details: models.RepoAppDetails) => { + const type = (explicitPathType && explicitPathType.path === appInEdit.spec.source.path && explicitPathType.type) || details.type; + if (details.type !== type) { + switch (type) { + case 'Helm': + details = { + type, + path: details.path, + helm: {name: '', valueFiles: [], path: '', parameters: [], fileParameters: []} + }; + break; + case 'Kustomize': + details = {type, path: details.path, kustomize: {path: ''}}; + break; + case 'Plugin': + details = {type, path: details.path, plugin: {name: '', env: []}}; + break; + // Directory + default: + details = {type, path: details.path, directory: {}}; + break; + } + } + return ( + + ( +

+ {type} +

+ )} + items={appTypes.map(item => ({ + title: item.type, + action: () => { + setExplicitPathType({type: item.type, path: appInEdit.spec.source.path}); + normalizeTypeFields(api, item.type); + } + }))} + /> + { + api.setAllValues(updatedApp); + }} + /> +
+ ); + }} +
+ ); + + return ( + + {sourcePanel()} + + {typePanel()} + + ); + }} + +
+ ); + }} +
+
+ ); +}; diff --git a/ui/src/app/applications/components/resource-details/resource-details.tsx b/ui/src/app/applications/components/resource-details/resource-details.tsx index 7b973f89fc8c9..59774fa459872 100644 --- a/ui/src/app/applications/components/resource-details/resource-details.tsx +++ b/ui/src/app/applications/components/resource-details/resource-details.tsx @@ -4,7 +4,7 @@ import {useState} from 'react'; import {EventsList, YamlEditor} from '../../../shared/components'; import * as models from '../../../shared/models'; import {ErrorBoundary} from '../../../shared/components/error-boundary/error-boundary'; -import {Context} from '../../../shared/context'; +import {AppContext, Context} from '../../../shared/context'; import {Application, ApplicationTree, Event, ResourceNode, State, SyncStatuses} from '../../../shared/models'; import {services} from '../../../shared/services'; import {ResourceTabExtension} from '../../../shared/services/extensions-service'; @@ -31,6 +31,7 @@ interface ResourceDetailsProps { isAppSelected: boolean; tree: ApplicationTree; tab?: string; + appCxt: AppContext; } export const ResourceDetails = (props: ResourceDetailsProps) => { @@ -178,6 +179,7 @@ export const ResourceDetails = (props: ResourceDetailsProps) => { setPageNumber={setPageNumber} collapsedSources={collapsedSources} handleCollapse={handleCollapse} + appContext={props.appCxt} /> ) }, diff --git a/ui/src/app/applications/components/utils.tsx b/ui/src/app/applications/components/utils.tsx index 844426d9ee074..7e12416754afa 100644 --- a/ui/src/app/applications/components/utils.tsx +++ b/ui/src/app/applications/components/utils.tsx @@ -7,7 +7,7 @@ import {FormApi, Text} from 'react-form'; import * as moment from 'moment'; import {BehaviorSubject, combineLatest, concat, from, fromEvent, Observable, Observer, Subscription} from 'rxjs'; import {debounceTime, map} from 'rxjs/operators'; -import {Context, ContextApis} from '../../shared/context'; +import {AppContext, Context, ContextApis} from '../../shared/context'; import {ResourceTreeNode} from './application-resource-tree/application-resource-tree'; import {CheckboxField, COLORS, ErrorNotification, Revision} from '../../shared/components'; @@ -346,6 +346,46 @@ const deletePodAction = async (ctx: ContextApis, pod: appModels.ResourceNode, ap ); }; +export const deleteSourceAction = (app: appModels.Application, source: appModels.ApplicationSource, appContext: AppContext) => { + appContext.apis.popup.prompt( + 'Delete source', + () => ( +
+

+ <> + Are you sure you want to delete the source with URL: {source.repoURL} + {source.path ? ( + <> + {' '} + and path: {source.path}? + + ) : ( + <>? + )} + +

+
+ ), + { + submit: async (vals, _, close) => { + try { + const i = app.spec.sources.indexOf(source); + app.spec.sources.splice(i, 1); + await services.applications.update(app); + close(); + } catch (e) { + appContext.apis.notifications.show({ + content: , + type: NotificationType.Error + }); + } + } + }, + {name: 'argo-icon-warning', color: 'warning'}, + 'yellow' + ); +}; + export const deletePopup = async ( ctx: ContextApis, resource: ResourceTreeNode, diff --git a/ui/src/app/shared/components/editable-panel/editable-section.tsx b/ui/src/app/shared/components/editable-panel/editable-section.tsx index 7a5ff63aed988..790390b6a6a24 100644 --- a/ui/src/app/shared/components/editable-panel/editable-section.tsx +++ b/ui/src/app/shared/components/editable-panel/editable-section.tsx @@ -4,6 +4,7 @@ import {Form, FormApi} from 'react-form'; import {ContextApis} from '../../context'; import {EditablePanelItem} from './editable-panel'; import {Spinner} from '../spinner'; +import {helpTip} from '../../../applications/components/utils'; export interface EditableSectionProps { title?: string | React.ReactNode; @@ -20,7 +21,9 @@ export interface EditableSectionProps { ctx: ContextApis; isTopSection?: boolean; disabledState?: boolean; + disabledDelete?: boolean; updateButtons?: (pressed: boolean) => void; + deleteSource?: () => void; } interface EditableSectionState { @@ -57,17 +60,32 @@ export class EditableSection extends React.Component {!this.state.isEditing && ( - +
+ {' '} + {this.props.isTopSection && this.props.deleteSource && ( + + )} +
)} {this.state.isEditing && (
From ba67abed40cd9831383afab7ba3479ae44804ff2 Mon Sep 17 00:00:00 2001 From: Alexander Matyushentsev Date: Thu, 5 Sep 2024 09:10:39 -0700 Subject: [PATCH 2/8] docs: proposal to introduce 'Prune/Delete=confirm' sync option value (#19520) * docs: proposal to introduce 'Prune/Delete=confirm' sync option value Signed-off-by: Alexander Matyushentsev * add clarifications Signed-off-by: Alexander Matyushentsev --------- Signed-off-by: Alexander Matyushentsev Co-authored-by: Dan Garfield --- .../resource-deletion-with-approval.md | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 docs/proposals/resource-deletion-with-approval.md diff --git a/docs/proposals/resource-deletion-with-approval.md b/docs/proposals/resource-deletion-with-approval.md new file mode 100644 index 0000000000000..4d843ce08fa38 --- /dev/null +++ b/docs/proposals/resource-deletion-with-approval.md @@ -0,0 +1,171 @@ +--- +title: Neat-enhancement-idea +authors: +- "@alexmt" + sponsors: +- TBD + reviewers: +- "@jessesuen" +- TBD + approvers: +- "@jessesuen" +- TBD + +creation-date: 2020-04-19 +last-updated: 2020-04-19 + +--- +# Neat Enhancement Idea + +Support manual approval for pruning and deleting Kubernetes resources during application syncing/deletion. + +## Summary + +Introduce Kubernetes resource-level annotations that require manual user approval using Argo CD UI/CLI/API before the +resource is pruned or deleted. The annotations should be respected while Argo CD attempts to synchronize or delete the +application. + +## Motivation + +We’ve seen cases where Argo CD deleted Kubernetes resources due to a bug or misconfiguration.​ Examples include [corrupted +data](https://github.com/argoproj/argo-cd/issues/4423) in Redis, user errors +([1](https://github.com/argoproj/argo-cd/issues/9093), [2](https://github.com/argoproj/argo-cd/issues/4844)) +and [bug](https://github.com/argoproj/argo-cd/issues/3473) in the automation on top of Argo CD. These examples don’t +mean Argo CD is not reliable; however, there are cases where misbehavior is catastrophic, and erroneous deletion is not +acceptable. Examples include the app-of-apps pattern where Argo CD is used to manage itself, or namespaces in production +clusters. + +### Goals + +The goals of a proposal ares: + +#### Allow developers to mark resources that require manual approval before application deletion. + +Developers should be able to add an annotation to resources that require manual approval before deletion. The annotation +should be respected by Argo CD when it attempts to delete the application. + +#### Allow developers to mark resources that require manual approval before pruning + +Developers should be able to add an annotation to resources that require manual approval before pruning. The annotation +should be respected by Argo CD when it attempts to prune extra resources while syncing the application. + +### Non-Goals + +#### Implement automatic self check while deleting resources + +We've made our best effort to implement corrected behavior, and as of now, we are not aware of any bugs that cause +erroneous deletion. The goal of this proposal is to provide a safety net for cases where deletion is not acceptable. + +## Proposal + +It is proposed to introduce two new sync options for Argo CD applications: `Prune=confirm` and `Delete=confirm`. Options would +protect resources from accidental deletion during cascading application deletion as well as during sync operations. + +### Introduce `confirm` option for Prune sync option. + +Argo CD already supports `argocd.argoproj.io/sync-options: Prune=false` sync option that prevents resource deletion while syncing +the application. This, however, is not ideal since it prevents implementing fully automated workflows that include resource deletion. + +In order to improve the situation, we propose to introduce `confirm` option for Prune sync option. When `confirm` option is set, Argo CD should pause the sync operation +**before deleting any app resources** and wait for the user to confirm the deletion. The confirmation can be done in a very friendly way using Argo CD UI, CLI or API. + +* **Sync Operation status**. I suggest not to introduce new sync operation states to avoid disturbing the existing automation around syncing (CI pipelines, scripts etc). + If Argo CD is waiting for the operation state should remain `Progressing`. Once the user confirms the deletion, the operation should resume. +* **Sync Waves**. The sync wave shuold be "paused" while Argo CD is waiting for the user to confirm the deletion. No difference from waiting for the resource to became healthy. + +### Introduce `confirm` option for Delete sync option. + +Similarly to `Prune` sync option we need to introduce `confirm` value for `Delete` sync option: `argocd.argoproj.io/sync-options: Delete=confirm`. The `confirm` option +should pause the sync operation **before deleting any app resources** and wait for the user to confirm the deletion. The confirmation can be done in a very friendly way +using Argo CD UI, CLI or API. + + +### Friendly prunning/deletion manual approval + +Since we know Argo CD is often used to implement fully automated developer workflows that include resource deletion, the +deletion approval process should be as painless as possible. This way, platform administrators can instruct end users to +apply the new prune/delete option to resources that require special care without significantly disturbing the developer +experience. + +In both cases where Argo CD requires manual approval, the user should be able to approve the deletion using Argo CD UI, +CLI, or API. The approval process should be as simple as possible and should not require the user to understand the +internals of Argo CD. + +#### New `requiresDeletionApproval` resource field in application status + +A new field `requiresDeletionApproval` should be added to the `status.resources` list items. The field should be set to `true` when the resource deletion approval is required. + +```yaml + - health: + status: Healthy + kind: Service + name: guestbook-ui + namespace: default + status: OutOfSync + version: v1 + requiresPruning: true + requiresDeletionApproval: true # new field that indicates that deletion approval is required +``` + +The Argo CD UI, CLI should visualize the `requiresDeletionApproval` field so that the user can easily discover which resources require manual approval. + +#### Approve deletion resource action + +The Argo CD UI, CLI should bundle the `Approve Deletion` [resource action](https://argo-cd.readthedocs.io/en/stable/operator-manual/resource_actions/) +that would allow the user to approve the deletion. The action should patch the resource with the `argocd.argoproj.io/deletion-approved: true` annotation. +Once annotation is applied the Argo CD should proceed with the deletion. + +The main reason to use the action is that we can reuse existing [RBAC](https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/) to control who can approve the deletion. + +#### UI/CLI Convinience to approve all resources + +The Argo CD UI should provide a convinient way to approve resources that require manual approval. The existing user interface will provide a button that allows end user +execute the `Approve Deletion` action and approve resources one by one. In addition to the single resource approval, the UI should provide a way to approve all resources +that require manual approval. The new button should execute the `Approve Deletion` action for all resources that require manual approval. + +Argo CD CLI would no need changes since existing `argocd app actions run` command allows to execute an action against multiple resources. + +#### Require deletion approval notification + +The default Argo CD notification catalog should include a trigger and notification template that notifies the user when +deletion approval is required. The notification template should include a list of resources that require approval. + + +#### Declarative approval + +The user should be able to approve resource deletion without using the UI or CLI by manually adding the `argocd.argoproj.io/deletion-approved: true` annotation to the resource. + +### Use cases + +Add a list of detailed use cases this enhancement intends to take care of. + +## Use case 1: + +As a developer, I would like to mark resources that require manual pruning approval so I can prevent the accidental deletion of critical resources. + +## Use case 2: + +As a developer, I would like to mark resources that require manual deletion approval so I can prevent the accidental deletion of critical resources. + + +### Security Considerations + +The resource approval would require a mechanism to control who can approve the deletion. The proposal to use +resource-level actions solves this problem and allows us to reuse the existing RBAC model. + +### Risks and Mitigations + +None. + +### Upgrade / Downgrade Strategy + +In case of rollback to the previous version the sync option would be ignored and the resources would be deleted as before. + +## Open Issues + +The proposal would require end users to learn about the new behavior and adjust their workflows. It includes a set of +enhancements aimed at minimizing the impact on end users. + +## Alternatives + +None. \ No newline at end of file From 9d3409f7d563b387c18707da9e2928772b09659a Mon Sep 17 00:00:00 2001 From: Dustin Lactin Date: Thu, 5 Sep 2024 10:44:58 -0600 Subject: [PATCH 3/8] docs: Add Mozilla to USERS.md (#19802) Signed-off-by: Dustin Lactin --- USERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/USERS.md b/USERS.md index d080e20d2bd1d..3236a8eee0c69 100644 --- a/USERS.md +++ b/USERS.md @@ -207,6 +207,7 @@ Currently, the following organizations are **officially** using Argo CD: 1. [Moengage](https://www.moengage.com/) 1. [Money Forward](https://corp.moneyforward.com/en/) 1. [MOO Print](https://www.moo.com/) +1. [Mozilla](https://www.mozilla.org) 1. [MTN Group](https://www.mtn.com/) 1. [Municipality of The Hague](https://www.denhaag.nl/) 1. [My Job Glasses](https://myjobglasses.com) From d28229dc1c260fd8ce85f07d1da255058b206563 Mon Sep 17 00:00:00 2001 From: Dan Garfield Date: Thu, 5 Sep 2024 12:54:50 -0600 Subject: [PATCH 4/8] Fix broken link from overview, previous merge conflict (#19801) Signed-off-by: todaywasawesome --- docs/developer-guide/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer-guide/index.md b/docs/developer-guide/index.md index 5ec3b87743717..08578c0fd0707 100644 --- a/docs/developer-guide/index.md +++ b/docs/developer-guide/index.md @@ -15,7 +15,7 @@ Need help? Start with the [Contributors FAQ](faq/) ## Contributing to the Documentation -* [Building and Running Documentation Site Locally](site/) +* [Building and Running Documentation Site Locally](docs-site/) ## Extensions and Third-Party Applications * [UI Extensions](ui-extensions/) From 01bbd91c9d7f73ed2df5a2daa5a228dd19d968a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 04:57:18 +0000 Subject: [PATCH 5/8] chore(deps): bump golang.org/x/net from 0.28.0 to 0.29.0 (#19808) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.28.0 to 0.29.0. - [Commits](https://github.com/golang/net/compare/v0.28.0...v0.29.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index ad1df2d37f05f..7ac85cd299454 100644 --- a/go.mod +++ b/go.mod @@ -83,9 +83,9 @@ require ( go.opentelemetry.io/otel v1.29.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 go.opentelemetry.io/otel/sdk v1.29.0 - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.27.0 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 - golang.org/x/net v0.28.0 + golang.org/x/net v0.29.0 golang.org/x/oauth2 v0.22.0 golang.org/x/sync v0.8.0 golang.org/x/term v0.24.0 @@ -151,7 +151,7 @@ require ( go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/api v0.132.0 // indirect google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect diff --git a/go.sum b/go.sum index 5811e70b16d9a..1fc9cbb37a9fe 100644 --- a/go.sum +++ b/go.sum @@ -1040,8 +1040,8 @@ golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOM golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= @@ -1125,8 +1125,8 @@ golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1269,8 +1269,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 09fdec4c6b8f1e302b9dad5405e9548c418cbcb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 09:22:14 +0200 Subject: [PATCH 6/8] chore(deps): bump golang.org/x/oauth2 from 0.22.0 to 0.23.0 (#19790) Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.22.0 to 0.23.0. - [Commits](https://github.com/golang/oauth2/compare/v0.22.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7ac85cd299454..4ae2859ab726c 100644 --- a/go.mod +++ b/go.mod @@ -86,7 +86,7 @@ require ( golang.org/x/crypto v0.27.0 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 golang.org/x/net v0.29.0 - golang.org/x/oauth2 v0.22.0 + golang.org/x/oauth2 v0.23.0 golang.org/x/sync v0.8.0 golang.org/x/term v0.24.0 golang.org/x/time v0.6.0 diff --git a/go.sum b/go.sum index 1fc9cbb37a9fe..93e63508ec812 100644 --- a/go.sum +++ b/go.sum @@ -1133,8 +1133,8 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From b84f01eb3dc7c7100a5f2032b0b47152d82846e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 09:23:07 +0200 Subject: [PATCH 7/8] chore(deps): bump peter-evans/create-pull-request from 7.0.0 to 7.0.1 (#19806) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/4320041ed380b20e97d388d56a7fb4f9b8c20e79...8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/init-release.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/init-release.yaml b/.github/workflows/init-release.yaml index d3ec4122db83f..2d2e4a149c411 100644 --- a/.github/workflows/init-release.yaml +++ b/.github/workflows/init-release.yaml @@ -64,7 +64,7 @@ jobs: git stash pop - name: Create pull request - uses: peter-evans/create-pull-request@4320041ed380b20e97d388d56a7fb4f9b8c20e79 # v7.0.0 + uses: peter-evans/create-pull-request@8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20 # v7.0.1 with: commit-message: "Bump version to ${{ inputs.TARGET_VERSION }}" title: "Bump version to ${{ inputs.TARGET_VERSION }} on ${{ inputs.TARGET_BRANCH }} branch" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index edb7410dcda30..eb0e905045f92 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -295,7 +295,7 @@ jobs: if: ${{ env.UPDATE_VERSION == 'true' }} - name: Create PR to update VERSION on master branch - uses: peter-evans/create-pull-request@4320041ed380b20e97d388d56a7fb4f9b8c20e79 # v7.0.0 + uses: peter-evans/create-pull-request@8867c4aba1b742c39f8d0ba35429c2dfa4b6cb20 # v7.0.1 with: commit-message: Bump version in master title: "chore: Bump version in master" From 3d66b05899f4af3ad30df3edd75ed68e36e0e705 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 09:24:00 +0200 Subject: [PATCH 8/8] chore(deps): bump tj-actions/changed-files from 44.5.7 to 45.0.1 (#19750) Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 44.5.7 to 45.0.1. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/c65cd883420fd2eb864698a825fc4162dd94482c...e9772d140489982e0e3704fea5ee93d536f1e275) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml index 92b6413a30f06..6d1032c416fb6 100644 --- a/.github/workflows/ci-build.yaml +++ b/.github/workflows/ci-build.yaml @@ -31,7 +31,7 @@ jobs: docs: ${{ steps.filter.outputs.docs_any_changed }} steps: - uses: actions/checkout@8410ad0602e1e429cee44a835ae9f77f654a6694 # v4.0.0 - - uses: tj-actions/changed-files@c65cd883420fd2eb864698a825fc4162dd94482c # v44.5.7 + - uses: tj-actions/changed-files@e9772d140489982e0e3704fea5ee93d536f1e275 # v45.0.1 id: filter with: # Any file which is not under docs/, ui/ or is not a markdown file is counted as a backend file