Compare commits

...

20 Commits

Author SHA1 Message Date
CrazyMax
3acf805d94 Merge pull request #345 from crazy-max/update-yarn
Some checks failed
test / test (push) Failing after 5s
validate / prepare (push) Failing after 5s
validate / validate (push) Has been skipped
ci / bake (default) (push) Failing after 8s
ci / bake (release) (push) Failing after 8s
ci / error-msg (push) Failing after 5s
ci / error-check (push) Failing after 4s
ci / standalone (push) Failing after 5s
ci / remote (push) Failing after 5s
ci / provenance () (push) Failing after 5s
ci / provenance (builder-id=foo) (push) Failing after 5s
ci / provenance (false) (push) Failing after 5s
ci / provenance (mode=max) (push) Failing after 5s
ci / provenance (true) (push) Failing after 5s
ci / sbom (/tmp/bake-build, binary) (push) Failing after 9s
ci / sbom (type=image,name=localhost:5000/name/app:latest,push=true, image) (push) Failing after 8s
ci / set (push) Failing after 8s
ci / group (push) Failing after 8s
ci / docker-config-malformed (push) Failing after 4s
ci / proxy-docker-config (push) Failing after 8s
ci / proxy-buildkitd (push) Failing after 8s
ci / git-context (push) Failing after 5s
ci / git-context-and-local (push) Failing after 5s
ci / multi-output (push) Failing after 8s
ci / load-and-push (push) Failing after 8s
ci / summary-disable (push) Failing after 5s
ci / summary-disable-deprecated (push) Failing after 5s
ci / summary-not-supported (push) Failing after 5s
ci / record-upload-disable (push) Failing after 4s
ci / record-retention-days (0) (push) Failing after 4s
ci / record-retention-days (2) (push) Failing after 5s
ci / export-legacy (false) (push) Failing after 5s
ci / export-legacy (true) (push) Failing after 5s
ci / checks (edge) (push) Failing after 5s
ci / checks (v0.14.1) (push) Failing after 5s
ci / annotations-disabled (push) Failing after 5s
ci / allow (edge) (push) Failing after 5s
ci / allow (v0.17.1) (push) Failing after 5s
ci / allow (v0.18.0) (push) Failing after 4s
ci / allow (v0.19.0) (push) Failing after 5s
ci / no-default-attestations (push) Failing after 5s
ci / call-check (push) Failing after 5s
ci / call-check-multi (push) Failing after 5s
ci / call-check-nowarning (push) Failing after 5s
ci-subaction / list-targets (["lint-default","lint-labs","lint-nydus","lint-proto","lint-yaml","validate-doctoc","validate-vendor"] , validate, group-matrix) (push) Failing after 5s
ci-subaction / list-targets (["t1","t2"] , group) (push) Failing after 5s
ci-subaction / list-targets (["v1-tag","v2-tag"] , docker-bake.json docker-bake.hcl , multi-files) (push) Failing after 5s
ci-subaction / matrix ([{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"darwin/amd64"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"… (push) Failing after 3s
ci-subaction / matrix ([{"target":"lint","platforms":"darwin/amd64"},{"target":"lint","platforms":"darwin/arm64"},{"target":"lint","platforms":"linux/amd64"},{"target":"lint","platforms":"linux/arm64"},{"target":"lint","platforms":"linux/s390x"},{"target":"lint","pla… (push) Failing after 3s
ci-subaction / matrix ([{"target":"lint"},{"target":"lint-gopls"},{"target":"validate-docs"},{"target":"validate-vendor"}] , validate, group-with-platform) (push) Failing after 5s
ci-subaction / matrix ([{"target":"lint-default"},{"target":"lint-labs"},{"target":"lint-nydus"},{"target":"lint-proto"},{"target":"lint-yaml"},{"target":"validate-doctoc"},{"target":"validate-vendor"}] , validate, group-matrix) (push) Failing after 5s
ci-subaction / matrix ([{"target":"t1"},{"target":"t2"}] , group) (push) Failing after 4s
update yarn to 4.9.2
2025-08-20 17:02:25 +02:00
CrazyMax
f6376ff6a4 update yarn to 4.9.2
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-08-20 16:56:24 +02:00
CrazyMax
60b3af61bc Merge pull request #344 from crazy-max/update-dev
update dev dependencies
2025-08-20 16:54:19 +02:00
CrazyMax
f08ef55f78 chore: update generated content
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-08-20 16:51:55 +02:00
CrazyMax
c22b3482ed update dev dependencies
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-08-20 16:51:03 +02:00
CrazyMax
40bffc195e Merge pull request #342 from docker/dependabot/npm_and_yarn/docker/actions-toolkit-0.63.0
chore(deps): Bump @docker/actions-toolkit from 0.62.1 to 0.63.0
2025-08-20 16:49:03 +02:00
CrazyMax
d7a0b5eb51 chore: update generated content
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-08-20 16:46:24 +02:00
dependabot[bot]
3965f4baf4 chore(deps): Bump @docker/actions-toolkit from 0.62.1 to 0.63.0
Bumps [@docker/actions-toolkit](https://github.com/docker/actions-toolkit) from 0.62.1 to 0.63.0.
- [Release notes](https://github.com/docker/actions-toolkit/releases)
- [Commits](https://github.com/docker/actions-toolkit/compare/v0.62.1...v0.63.0)

---
updated-dependencies:
- dependency-name: "@docker/actions-toolkit"
  dependency-version: 0.63.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 14:43:32 +00:00
CrazyMax
3d864c34f9 Merge pull request #341 from docker/dependabot/npm_and_yarn/tmp-0.2.4
chore(deps): Bump tmp from 0.2.3 to 0.2.4
2025-08-20 16:41:23 +02:00
CrazyMax
3589b0c183 chore: update generated content
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-08-20 16:39:06 +02:00
dependabot[bot]
2215fae6ab chore(deps): Bump tmp from 0.2.3 to 0.2.4
Bumps [tmp](https://github.com/raszi/node-tmp) from 0.2.3 to 0.2.4.
- [Changelog](https://github.com/raszi/node-tmp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/raszi/node-tmp/compare/v0.2.3...v0.2.4)

---
updated-dependencies:
- dependency-name: tmp
  dependency-version: 0.2.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 14:37:49 +00:00
CrazyMax
d0cb9774dc Merge pull request #340 from docker/dependabot/npm_and_yarn/undici-5.29.0
chore(deps): Bump undici from 5.28.4 to 5.29.0
2025-08-20 16:35:21 +02:00
CrazyMax
a6c0a91e79 chore: update generated content
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-08-20 16:31:47 +02:00
dependabot[bot]
74cb41d332 chore(deps): Bump undici from 5.28.4 to 5.29.0
Bumps [undici](https://github.com/nodejs/undici) from 5.28.4 to 5.29.0.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.28.4...v5.29.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 5.29.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 14:29:38 +00:00
CrazyMax
8f035c08cd Merge pull request #339 from docker/dependabot/npm_and_yarn/brace-expansion-1.1.12
chore(deps): Bump brace-expansion from 1.1.11 to 1.1.12
2025-08-20 16:27:16 +02:00
CrazyMax
a4c28defc8 chore: update generated content
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-08-20 16:25:20 +02:00
dependabot[bot]
d260a27f75 chore(deps): Bump brace-expansion from 1.1.11 to 1.1.12
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 1.1.11 to 1.1.12.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/1.1.11...v1.1.12)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-20 14:23:25 +00:00
CrazyMax
5efdf4de8d Merge pull request #338 from crazy-max/call-input
call input to set method for evaluating build
2025-08-20 16:19:54 +02:00
CrazyMax
7a78e75fa4 chore: update generated content
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-08-14 09:50:47 +02:00
CrazyMax
24d8c3471c call input to set method for evaluating build
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-08-14 09:48:36 +02:00
12 changed files with 2463 additions and 2753 deletions

View File

@@ -116,7 +116,6 @@ jobs:
- -
name: Check name: Check
run: | run: |
echo "${{ toJson(steps.bake) }}"
if [ "${{ steps.bake.outcome }}" != "failure" ] || [ "${{ steps.bake.conclusion }}" != "success" ]; then if [ "${{ steps.bake.outcome }}" != "failure" ] || [ "${{ steps.bake.conclusion }}" != "success" ]; then
echo "::error::Should have failed" echo "::error::Should have failed"
exit 1 exit 1
@@ -779,3 +778,92 @@ jobs:
./test/config.hcl ./test/config.hcl
env: env:
BUILDX_NO_DEFAULT_ATTESTATIONS: 1 BUILDX_NO_DEFAULT_ATTESTATIONS: 1
call-check:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v5
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
version: ${{ inputs.buildx-version || env.BUILDX_VERSION }}
driver-opts: |
image=${{ inputs.buildkit-image || env.BUILDKIT_IMAGE }}
-
name: Build
id: bake
continue-on-error: true
uses: ./
with:
workdir: ./test
source: .
files: |
./lint.hcl
call: check
targets: lint
-
name: Check
run: |
if [ "${{ steps.bake.outcome }}" != "failure" ] || [ "${{ steps.bake.conclusion }}" != "success" ]; then
echo "::error::Should have failed"
exit 1
fi
call-check-multi:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v5
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
version: ${{ inputs.buildx-version || env.BUILDX_VERSION }}
driver-opts: |
image=${{ inputs.buildkit-image || env.BUILDKIT_IMAGE }}
-
name: Build
id: bake
continue-on-error: true
uses: ./
with:
workdir: ./test
source: .
files: |
./lint.hcl
call: check
-
name: Check
run: |
if [ "${{ steps.bake.outcome }}" != "failure" ] || [ "${{ steps.bake.conclusion }}" != "success" ]; then
echo "::error::Should have failed"
exit 1
fi
call-check-nowarning:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v5
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
version: ${{ inputs.buildx-version || env.BUILDX_VERSION }}
driver-opts: |
image=${{ inputs.buildkit-image || env.BUILDKIT_IMAGE }}
-
name: Build
id: bake
continue-on-error: true
uses: ./
with:
source: .
files: |
./test/config.hcl
call: check

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,9 @@
# https://yarnpkg.com/configuration/yarnrc
compressionLevel: mixed
enableGlobalCache: false
enableHardenedMode: true
logFilters: logFilters:
- code: YN0013 - code: YN0013
level: discard level: discard
@@ -5,9 +11,7 @@ logFilters:
level: discard level: discard
- code: YN0076 - code: YN0076
level: discard level: discard
- code: YN0086
level: discard
nodeLinker: node-modules nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"

View File

@@ -200,11 +200,11 @@ The following inputs can be used as `step.with` keys
| Name | Type | Description | | Name | Type | Description |
|----------------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| |----------------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `builder` | String | Builder instance (see [setup-buildx](https://github.com/docker/setup-buildx-action) action) | | `builder` | String | Builder instance (see [setup-buildx](https://github.com/docker/setup-buildx-action) action) |
| `workdir` | String | Working directory of execution |
| `source` | String | Context to build from. Can be either local (`.`) or a [remote bake definition](https://docs.docker.com/build/bake/remote-definition/) | | `source` | String | Context to build from. Can be either local (`.`) or a [remote bake definition](https://docs.docker.com/build/bake/remote-definition/) |
| `allow` | List/CSV | Allow build to access specified resources (e.g., `network.host`) | | `allow` | List/CSV | Allow build to access specified resources (e.g., `network.host`) |
| `call` | String | Set method for evaluating build (e.g., check) |
| `files` | List/CSV | List of [bake definition files](https://docs.docker.com/build/customize/bake/file-definition/) | | `files` | List/CSV | List of [bake definition files](https://docs.docker.com/build/customize/bake/file-definition/) |
| `workdir` | String | Working directory of execution |
| `targets` | List/CSV | List of bake targets (`default` target used if empty) |
| `no-cache` | Bool | Do not use cache when building the image (default `false`) | | `no-cache` | Bool | Do not use cache when building the image (default `false`) |
| `pull` | Bool | Always attempt to pull a newer version of the image (default `false`) | | `pull` | Bool | Always attempt to pull a newer version of the image (default `false`) |
| `load` | Bool | Load is a shorthand for `--set=*.output=type=docker` (default `false`) | | `load` | Bool | Load is a shorthand for `--set=*.output=type=docker` (default `false`) |
@@ -212,6 +212,7 @@ The following inputs can be used as `step.with` keys
| `push` | Bool | Push is a shorthand for `--set=*.output=type=registry` (default `false`) | | `push` | Bool | Push is a shorthand for `--set=*.output=type=registry` (default `false`) |
| `sbom` | Bool/String | [SBOM](https://docs.docker.com/build/attestations/sbom/) is a shorthand for `--set=*.attest=type=sbom` | | `sbom` | Bool/String | [SBOM](https://docs.docker.com/build/attestations/sbom/) is a shorthand for `--set=*.attest=type=sbom` |
| `set` | List | List of [targets values to override](https://docs.docker.com/engine/reference/commandline/buildx_bake/#set) (e.g., `targetpattern.key=value`) | | `set` | List | List of [targets values to override](https://docs.docker.com/engine/reference/commandline/buildx_bake/#set) (e.g., `targetpattern.key=value`) |
| `targets` | List/CSV | List of bake targets (`default` target used if empty) |
| `github-token` | String | API token used to authenticate to a Git repository for [remote definitions](https://docs.docker.com/build/bake/remote-definition/) (default `${{ github.token }}`) | | `github-token` | String | API token used to authenticate to a Git repository for [remote definitions](https://docs.docker.com/build/bake/remote-definition/) (default `${{ github.token }}`) |
### outputs ### outputs

View File

@@ -10,22 +10,22 @@ inputs:
builder: builder:
description: "Builder instance" description: "Builder instance"
required: false required: false
workdir:
description: "Working directory of bake execution"
required: false
default: '.'
source: source:
description: "Context to build from. Can be either local or a remote bake definition" description: "Context to build from. Can be either local or a remote bake definition"
required: false required: false
allow: allow:
description: "Allow build to access specified resources (e.g., network.host)" description: "Allow build to access specified resources (e.g., network.host)"
required: false required: false
call:
description: "Set method for evaluating build (e.g., check)"
required: false
files: files:
description: "List of bake definition files" description: "List of bake definition files"
required: false required: false
workdir:
description: "Working directory of bake execution"
required: false
default: '.'
targets:
description: "List of bake targets"
required: false
no-cache: no-cache:
description: "Do not use cache when building the image" description: "Do not use cache when building the image"
required: false required: false
@@ -51,6 +51,9 @@ inputs:
set: set:
description: "List of targets values to override (eg. targetpattern.key=value)" description: "List of targets values to override (eg. targetpattern.key=value)"
required: false required: false
targets:
description: "List of bake targets"
required: false
github-token: github-token:
description: "API token used to authenticate to a Git repository for remote definitions" description: "API token used to authenticate to a Git repository for remote definitions"
default: ${{ github.token }} default: ${{ github.token }}

42
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

2
dist/sourcemap-register.js generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -24,25 +24,25 @@
], ],
"author": "Docker Inc.", "author": "Docker Inc.",
"license": "Apache-2.0", "license": "Apache-2.0",
"packageManager": "yarn@3.6.3", "packageManager": "yarn@4.9.2",
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1", "@actions/core": "^1.11.1",
"@docker/actions-toolkit": "^0.62.1", "@docker/actions-toolkit": "^0.63.0",
"handlebars": "^4.7.8" "handlebars": "^4.7.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.12.12", "@types/node": "^20.19.9",
"@typescript-eslint/eslint-plugin": "^7.9.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.9.0", "@typescript-eslint/parser": "^7.18.0",
"@vercel/ncc": "^0.38.1", "@vercel/ncc": "^0.38.3",
"eslint": "^8.57.0", "eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.2",
"eslint-plugin-jest": "^28.5.0", "eslint-plugin-jest": "^28.14.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.5.4",
"jest": "^29.7.0", "jest": "^29.7.0",
"prettier": "^3.2.5", "prettier": "^3.6.2",
"ts-jest": "^29.1.2", "ts-jest": "^29.4.1",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.4.5" "typescript": "^5.9.2"
} }
} }

View File

@@ -11,11 +11,12 @@ import {Util} from '@docker/actions-toolkit/lib/util';
import {BakeDefinition} from '@docker/actions-toolkit/lib/types/buildx/bake'; import {BakeDefinition} from '@docker/actions-toolkit/lib/types/buildx/bake';
export interface Inputs { export interface Inputs {
allow: string[];
builder: string; builder: string;
files: string[];
workdir: string; workdir: string;
targets: string[]; source: string;
allow: string[];
call: string;
files: string[];
'no-cache': boolean; 'no-cache': boolean;
pull: boolean; pull: boolean;
load: boolean; load: boolean;
@@ -23,17 +24,18 @@ export interface Inputs {
push: boolean; push: boolean;
sbom: string; sbom: string;
set: string[]; set: string[];
source: string; targets: string[];
'github-token': string; 'github-token': string;
} }
export async function getInputs(): Promise<Inputs> { export async function getInputs(): Promise<Inputs> {
return { return {
allow: Util.getInputList('allow'),
builder: core.getInput('builder'), builder: core.getInput('builder'),
files: Util.getInputList('files'),
workdir: core.getInput('workdir') || '.', workdir: core.getInput('workdir') || '.',
targets: Util.getInputList('targets'), source: getSourceInput('source'),
allow: Util.getInputList('allow'),
call: core.getInput('call'),
files: Util.getInputList('files'),
'no-cache': core.getBooleanInput('no-cache'), 'no-cache': core.getBooleanInput('no-cache'),
pull: core.getBooleanInput('pull'), pull: core.getBooleanInput('pull'),
load: core.getBooleanInput('load'), load: core.getBooleanInput('load'),
@@ -41,7 +43,7 @@ export async function getInputs(): Promise<Inputs> {
push: core.getBooleanInput('push'), push: core.getBooleanInput('push'),
sbom: core.getInput('sbom'), sbom: core.getInput('sbom'),
set: Util.getInputList('set', {ignoreComma: true, quote: false}), set: Util.getInputList('set', {ignoreComma: true, quote: false}),
source: getSourceInput('source'), targets: Util.getInputList('targets'),
'github-token': core.getInput('github-token') 'github-token': core.getInput('github-token')
}; };
} }
@@ -69,6 +71,12 @@ async function getBakeArgs(inputs: Inputs, definition: BakeDefinition, toolkit:
args.push('--allow', allow); args.push('--allow', allow);
}); });
} }
if (inputs.call) {
if (!(await toolkit.buildx.versionSatisfies('>=0.16.0'))) {
throw new Error(`Buildx >= 0.16.0 is required to use the call flag.`);
}
args.push('--call', inputs.call);
}
await Util.asyncForEach(inputs.files, async file => { await Util.asyncForEach(inputs.files, async file => {
args.push('--file', file); args.push('--file', file);
}); });

View File

@@ -146,8 +146,26 @@ actionsToolkit.run(
env: buildEnv, env: buildEnv,
ignoreReturnCode: true ignoreReturnCode: true
}).then(res => { }).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) { if (res.exitCode != 0) {
err = Error(`buildx bake failed with: ${res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error'}`); if (inputs.call && inputs.call === 'check' && res.stdout.length > 0) {
// checks warnings are printed to stdout: https://github.com/docker/buildx/pull/2647
// with bake we can have multiple targets being checked so we need to
// count the total number of warnings
const totalWarnings = [...res.stdout.matchAll(/^Check complete, (\d+) warnings? (?:has|have) been found!/gm)].reduce((sum, m) => sum + parseInt(m[1], 10), 0);
if (totalWarnings > 0) {
// https://github.com/docker/buildx/blob/1e50e8ddabe108f009b9925e13a321d7c8f99f26/commands/build.go#L797-L803
if (totalWarnings === 1) {
err = Error(`Check complete, ${totalWarnings} warning has been found!`);
} else {
err = Error(`Check complete, ${totalWarnings} warnings have been found!`);
}
} else {
// if there are no warnings found, return the first line of stdout
err = Error(res.stdout.split('\n')[0]?.trim());
}
} else if (res.stderr.length > 0) {
err = Error(`buildx bake failed with: ${res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error'}`);
}
} }
}); });
@@ -191,6 +209,8 @@ actionsToolkit.run(
await core.group(`Check build summary support`, async () => { await core.group(`Check build summary support`, async () => {
if (!buildSummaryEnabled()) { if (!buildSummaryEnabled()) {
core.info('Build summary disabled'); core.info('Build summary disabled');
} else if (inputs.call && inputs.call !== 'build') {
core.info(`Build summary skipped for ${inputs.call} subrequest`);
} else if (GitHub.isGHES) { } else if (GitHub.isGHES) {
core.info('Build summary is not yet supported on GHES'); core.info('Build summary is not yet supported on GHES');
} else if (!(await toolkit.buildx.versionSatisfies('>=0.13.0'))) { } else if (!(await toolkit.buildx.versionSatisfies('>=0.13.0'))) {

4431
yarn.lock

File diff suppressed because it is too large Load Diff