mirror of https://github.com/portainer/portainer
				
				
				
			feat(helm): make the atomic flag optional [r8s-314] (#733)
							parent
							
								
									4ee349bd6b
								
							
						
					
					
						commit
						d49fcd8f3e
					
				| 
						 | 
				
			
			@ -27,6 +27,7 @@ type installChartPayload struct {
 | 
			
		|||
	Repo      string `json:"repo"`
 | 
			
		||||
	Values    string `json:"values"`
 | 
			
		||||
	Version   string `json:"version"`
 | 
			
		||||
	Atomic    bool   `json:"atomic"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var errChartNameInvalid = errors.New("invalid chart name. " +
 | 
			
		||||
| 
						 | 
				
			
			@ -105,6 +106,7 @@ func (handler *Handler) installChart(r *http.Request, p installChartPayload) (*r
 | 
			
		|||
		Version:                 p.Version,
 | 
			
		||||
		Namespace:               p.Namespace,
 | 
			
		||||
		Repo:                    p.Repo,
 | 
			
		||||
		Atomic:                  p.Atomic,
 | 
			
		||||
		KubernetesClusterAccess: clusterAccess,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ import { Input } from '@@/form-components/Input';
 | 
			
		|||
import { CodeEditor } from '@@/CodeEditor';
 | 
			
		||||
import { FormControl } from '@@/form-components/FormControl';
 | 
			
		||||
import { WidgetTitle } from '@@/Widget';
 | 
			
		||||
import { Checkbox } from '@@/form-components/Checkbox';
 | 
			
		||||
 | 
			
		||||
import { UpdateHelmReleasePayload } from '../queries/useUpdateHelmReleaseMutation';
 | 
			
		||||
import { ChartVersion } from '../queries/useHelmRepositories';
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +38,7 @@ export function UpgradeHelmModal({ values, versions, onSubmit }: Props) {
 | 
			
		|||
    versionOptions[0]?.value;
 | 
			
		||||
  const [version, setVersion] = useState<ChartVersion>(defaultVersion);
 | 
			
		||||
  const [userValues, setUserValues] = useState<string>(values.values || '');
 | 
			
		||||
 | 
			
		||||
  const [atomic, setAtomic] = useState<boolean>(false);
 | 
			
		||||
  return (
 | 
			
		||||
    <Modal
 | 
			
		||||
      onDismiss={() => onSubmit()}
 | 
			
		||||
| 
						 | 
				
			
			@ -88,6 +89,20 @@ export function UpgradeHelmModal({ values, versions, onSubmit }: Props) {
 | 
			
		|||
              data-cy="helm-namespace-input"
 | 
			
		||||
            />
 | 
			
		||||
          </FormControl>
 | 
			
		||||
          <FormControl
 | 
			
		||||
            label="Rollback on failure"
 | 
			
		||||
            tooltip="Enables automatic rollback on failure (equivalent to the helm --atomic flag). It may increase the time to upgrade."
 | 
			
		||||
            inputId="atomic-input"
 | 
			
		||||
            className="[&>label]:!pl-0"
 | 
			
		||||
            size="medium"
 | 
			
		||||
          >
 | 
			
		||||
            <Checkbox
 | 
			
		||||
              id="atomic-input"
 | 
			
		||||
              checked={atomic}
 | 
			
		||||
              data-cy="atomic-checkbox"
 | 
			
		||||
              onChange={(e) => setAtomic(e.target.checked)}
 | 
			
		||||
            />
 | 
			
		||||
          </FormControl>
 | 
			
		||||
          <FormControl
 | 
			
		||||
            label="User-defined values"
 | 
			
		||||
            inputId="user-values-editor"
 | 
			
		||||
| 
						 | 
				
			
			@ -125,6 +140,7 @@ export function UpgradeHelmModal({ values, versions, onSubmit }: Props) {
 | 
			
		|||
                chart: values.chart,
 | 
			
		||||
                repo: version.Repo,
 | 
			
		||||
                version: version.Version,
 | 
			
		||||
                atomic,
 | 
			
		||||
              })
 | 
			
		||||
            }
 | 
			
		||||
            color="primary"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -218,7 +218,9 @@ describe('HelmEventsDatatable', () => {
 | 
			
		|||
 | 
			
		||||
    await waitFor(() => {
 | 
			
		||||
      expect(
 | 
			
		||||
        screen.getByText('Events reflect the latest revision only.')
 | 
			
		||||
        screen.getByText(
 | 
			
		||||
          'Only events for resources currently in the cluster will be displayed.'
 | 
			
		||||
        )
 | 
			
		||||
      ).toBeInTheDocument();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,8 @@ export function HelmEventsDatatable({
 | 
			
		|||
        dataset={eventsQuery.data || []}
 | 
			
		||||
        title={
 | 
			
		||||
          <TextTip inline color="blue" className="!text-xs">
 | 
			
		||||
            Events reflect the latest revision only.
 | 
			
		||||
            Only events for resources currently in the cluster will be
 | 
			
		||||
            displayed.
 | 
			
		||||
          </TextTip>
 | 
			
		||||
        }
 | 
			
		||||
        titleIcon={null}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -167,9 +167,13 @@ describe('ResourcesTable', () => {
 | 
			
		|||
    );
 | 
			
		||||
 | 
			
		||||
    // Check that success badge is rendered
 | 
			
		||||
    const successBadge = screen.getByText('MinimumReplicasAvailable');
 | 
			
		||||
    const successBadge = screen.getByText(
 | 
			
		||||
      (content, element) =>
 | 
			
		||||
        content.includes('MinimumReplicasAvailable') &&
 | 
			
		||||
        element !== null &&
 | 
			
		||||
        element.className.includes('bg-success')
 | 
			
		||||
    );
 | 
			
		||||
    expect(successBadge).toBeInTheDocument();
 | 
			
		||||
    expect(successBadge.className).toContain('bg-success');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should show error badges for failed resources', () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -177,8 +181,12 @@ describe('ResourcesTable', () => {
 | 
			
		|||
    expect(screen.getByText('probe-failure-nginx-bad')).toBeInTheDocument();
 | 
			
		||||
 | 
			
		||||
    // Check for the unhealthy status badge and make sure it has the error styling
 | 
			
		||||
    const errorBadge = screen.getByText('InsufficientPods');
 | 
			
		||||
    const errorBadge = screen.getByText(
 | 
			
		||||
      (content, element) =>
 | 
			
		||||
        content.includes('InsufficientPods') &&
 | 
			
		||||
        element !== null &&
 | 
			
		||||
        element.className.includes('bg-error')
 | 
			
		||||
    );
 | 
			
		||||
    expect(errorBadge).toBeInTheDocument();
 | 
			
		||||
    expect(errorBadge.className).toContain('bg-error');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,7 +59,7 @@ export function ResourcesTable() {
 | 
			
		|||
        emptyContentLabel="No resources found"
 | 
			
		||||
        title={
 | 
			
		||||
          <TextTip inline color="blue" className="!text-xs">
 | 
			
		||||
            Resources reflect the latest revision only.
 | 
			
		||||
            Only resources currently in the cluster will be displayed.
 | 
			
		||||
          </TextTip>
 | 
			
		||||
        }
 | 
			
		||||
        disableSelect
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,20 @@
 | 
			
		|||
import { Row } from '@tanstack/react-table';
 | 
			
		||||
 | 
			
		||||
import { filterHOC } from '@@/datatables/Filter';
 | 
			
		||||
 | 
			
		||||
import { ResourceRow } from '../types';
 | 
			
		||||
 | 
			
		||||
import { columnHelper } from './helper';
 | 
			
		||||
 | 
			
		||||
export const resourceType = columnHelper.accessor((row) => row.resourceType, {
 | 
			
		||||
  header: 'Resource type',
 | 
			
		||||
  id: 'resourceType',
 | 
			
		||||
  meta: {
 | 
			
		||||
    filter: filterHOC('Filter by resource type'),
 | 
			
		||||
  },
 | 
			
		||||
  enableColumnFilter: true,
 | 
			
		||||
  filterFn: (row: Row<ResourceRow>, _: string, filterValue: string[]) =>
 | 
			
		||||
    filterValue.length === 0 ||
 | 
			
		||||
    (!!row.original.resourceType &&
 | 
			
		||||
      filterValue.includes(row.original.resourceType)),
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
import { CellContext } from '@tanstack/react-table';
 | 
			
		||||
import { CellContext, Row } from '@tanstack/react-table';
 | 
			
		||||
 | 
			
		||||
import { StatusBadge } from '@@/StatusBadge';
 | 
			
		||||
import { filterHOC } from '@@/datatables/Filter';
 | 
			
		||||
 | 
			
		||||
import { ResourceRow } from '../types';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +11,21 @@ export const status = columnHelper.accessor((row) => row.status.label, {
 | 
			
		|||
  header: 'Status',
 | 
			
		||||
  id: 'status',
 | 
			
		||||
  cell: Cell,
 | 
			
		||||
  meta: {
 | 
			
		||||
    filter: filterHOC(
 | 
			
		||||
      'Filter by status',
 | 
			
		||||
      // don't include empty values in the filter options
 | 
			
		||||
      (rows: Row<ResourceRow>[]) =>
 | 
			
		||||
        Array.from(
 | 
			
		||||
          new Set(rows.map((row) => row.original.status.label).filter(Boolean))
 | 
			
		||||
        )
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  enableColumnFilter: true,
 | 
			
		||||
  filterFn: (row: Row<ResourceRow>, _: string, filterValue: string[]) =>
 | 
			
		||||
    filterValue.length === 0 ||
 | 
			
		||||
    (!!row.original.status.label &&
 | 
			
		||||
      filterValue.includes(row.original.status.label)),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function Cell({ row }: CellContext<ResourceRow, string>) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,7 @@ export interface UpdateHelmReleasePayload {
 | 
			
		|||
  name: string;
 | 
			
		||||
  chart: string;
 | 
			
		||||
  version?: string;
 | 
			
		||||
  atomic?: boolean;
 | 
			
		||||
}
 | 
			
		||||
export function useUpdateHelmReleaseMutation(environmentId: EnvironmentId) {
 | 
			
		||||
  const queryClient = useQueryClient();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ type InstallOptions struct {
 | 
			
		|||
	Wait                    bool
 | 
			
		||||
	ValuesFile              string
 | 
			
		||||
	PostRenderer            string
 | 
			
		||||
	Atomic                  bool
 | 
			
		||||
	Timeout                 time.Duration
 | 
			
		||||
	KubernetesClusterAccess *KubernetesClusterAccess
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -133,7 +133,7 @@ func (hspm *HelmSDKPackageManager) Upgrade(upgradeOpts options.InstallOptions) (
 | 
			
		|||
func initUpgradeClient(actionConfig *action.Configuration, upgradeOpts options.InstallOptions) (*action.Upgrade, error) {
 | 
			
		||||
	upgradeClient := action.NewUpgrade(actionConfig)
 | 
			
		||||
	upgradeClient.DependencyUpdate = true
 | 
			
		||||
	upgradeClient.Atomic = true
 | 
			
		||||
	upgradeClient.Atomic = upgradeOpts.Atomic
 | 
			
		||||
	upgradeClient.ChartPathOptions.RepoURL = upgradeOpts.Repo
 | 
			
		||||
	upgradeClient.Wait = upgradeOpts.Wait
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue