diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0996fe6..a4d30cc 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -15,6 +15,9 @@ on: - "action.yml" pull_request: +env: + httpbin: ${{ !github.event.act && 'https://httpbin.org' || 'http://192.168.1.25:8080' }} + jobs: test: name: "Test" @@ -32,23 +35,54 @@ jobs: GITHUB_CTX: ${{ toJSON(github) }} run: echo "$GITHUB_CTX" - - name: "Test Local Action" - id: test + - name: "1: Test Data" + id: test1 uses: ./ with: - url: https://httpbin.org/post + url: "${{ env.httpbin }}/post" + #insecure: true #data: '{"key": "value"}' data: | key: value params: | param1: value1 + config: | + timeout: 30000 - - name: "Verify Outputs" + - name: "1: Verify Data" + if: ${{ !github.event.act }} + run: | + echo 'status: ${{ steps.test1.outputs.status }}' + echo 'headers: ${{ steps.test1.outputs.headers }}' + echo 'data: ${{ steps.test1.outputs.data }}' + if [ '${{ fromJson(steps.test1.outputs.data).data }}' != '{"key":"value"}' ];then + echo "Output Invalid" + exit 1 + fi + + - name: "2: Test File" + if: ${{ !github.event.act }} + id: test2 + uses: ./ + with: + url: "${{ env.httpbin }}/post" + #insecure: true + #data: '{"key": "value"}' + data: | + key: value + params: | + param1: value1 + file: event.json + filename: not-event.json + + - name: "2: Verify File" + if: ${{ !github.event.act }} run: | - echo 'status: ${{ steps.test.outputs.status }}' - echo 'headers: ${{ steps.test.outputs.headers }}' - echo 'data: ${{ steps.test.outputs.data }}' - if [ '${{ fromJson(steps.test.outputs.data).data }}' != '{"key":"value"}' ];then + echo 'status: ${{ steps.test2.outputs.status }}' + echo 'headers: ${{ steps.test2.outputs.headers }}' + echo 'data: ${{ steps.test2.outputs.data }}' + echo 'data.form: ${{ fromJson(steps.test2.outputs.data).form.key }}' + if [ '${{ fromJson(steps.test2.outputs.data).form.key }}' != 'value' ];then echo "Output Invalid" exit 1 fi diff --git a/README.md b/README.md index 576455d..10d51a8 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,14 @@ Pass data/headers/params as JSON or YAML formatted strings. { "key": "value" } + config: | + timeout: 1000 username: ${{ secrets.USERNAME }} password: ${{ secrets.PASSWORD }} insecure: false file: path/to/file.txt name: file + filename: custom-name.txt ``` > [!NOTE] @@ -58,18 +61,20 @@ Pass data/headers/params as JSON or YAML formatted strings. ## Inputs -| Input | Default | Description of the Input Value | -| :------- | :--------- | :------------------------------------------------- | -| url | _Required_ | URL for Request [⤵️](#url) | -| method | `POST` | Request Method [⤵️](#method) | -| data | - | Request Data JSON/YAML [⤵️](#data) | -| headers | - | Request Headers JSON/YAML [⤵️](#headers) | -| params | - | Request Parameters JSON/YAML [⤵️](#params) | -| username | - | Basic Auth Username | -| password | - | Basic Auth Password | -| insecure | `false` | Ignore SSL Errors | -| file | - | File Path to Send [⤵️](#file) | -| name | `file` | File Form Key Name | +| Input | Default Value | Description of the Input Value | +| :------- | :----------------- | :------------------------------------------------- | +| url | _Required_ | URL for Request [⤵️](#url) | +| method | `POST` | Request Method [⤵️](#method) | +| data | - | Request Data JSON/YAML [⤵️](#data) | +| headers | - | Request Headers JSON/YAML [⤵️](#headers) | +| params | - | Request Parameters JSON/YAML [⤵️](#params) | +| config | - | Axios Config JSON/YAML [⤵️](#config) | +| username | - | Basic Auth Username | +| password | - | Basic Auth Password | +| insecure | `false` | Ignore SSL Errors | +| file | - | File Path to Send [⤵️](#file) | +| name | `file` | File Form Key Name | +| filename | _Original Name_ | Set a Different File Name | ### url @@ -77,18 +82,20 @@ The URL to send the request too. You may include params here or in the [params]( ### method -The request method, including custom methods. +The request method, including custom methods. Case-insensitive. Default: `POST` ### data -Body JSON or YAML data. Only used for `PUT`, `POST`, `DELETE`, and `PATCH`. +Body JSON/YAML data. Only used for `PUT`, `POST`, `DELETE`, and `PATCH`. Data is parsed with `JSON.parse` or `yaml.load`, [js-yaml](https://github.com/nodeca/js-yaml).
View JSON/YAML Example +This format works for `data`, `headers`, `params`, and `config`. + ```yaml data: | key1: value1 @@ -103,15 +110,35 @@ data: | } ``` +```yaml +data: '{"key1": "value1", "key2": "value2"}' +``` + +Note: All these examples are identical. +
### headers -Headers JSON or YAML data. +Headers JSON/YAML data. ### params -Parameters, Query String, JSON or YAML data. These may also be provided in the [url](#url). +Parameters (Query String) JSON/YAML data. These may also be provided in the [url](#url). + +### config + +Additional Axios Config JSON/YAML data. For example, set a 3-second timeout: `timeout: 3000` + +Reference: https://axios-http.com/docs/req_config + +
Note: The config is spread last and overrides other keys. + +```javascript +config = { url, method, headers, params, data, auth, httpsAgent, ...config } +``` + +
### file @@ -120,12 +147,7 @@ key `name`. The file path is relative to the workspace/working directory. For more information on inputs, see: https://axios-http.com/docs/req_config -```yaml -- name: 'Web Request' - uses: cssnr/web-request-action@v1 - with: - url: https://httpbin.org/post -``` +See the [Examples](#examples) for more usage options... ## Outputs @@ -135,6 +157,8 @@ For more information on inputs, see: https://axios-http.com/docs/req_config | headers | Response Headers | | data | Response Data | +Note: All outputs are run through `JSON.stringify` by default. + ```yaml - name: 'Web Request' id: test @@ -153,10 +177,20 @@ For more information on inputs, see: https://axios-http.com/docs/req_config 💡 _Click on an example heading to expand or collapse the example._ -
Algolia Start Crawl +
Trigger a Webhook ```yaml -- name: 'Algolia Start Crawl' +- name: 'Portainer Webhook' + uses: cssnr/web-request-action@v1 + with: + url: ${{ secrets.PORTAINER_WEBHOOK }} +``` + +
+
Start Algolia Crawl + +```yaml +- name: 'Start Algolia Crawl' uses: cssnr/web-request-action@v1 with: url: https://crawler.algolia.com/api/1/crawlers/${{ secrets.CRAWLER_ID }}/reindex @@ -168,7 +202,7 @@ For more information on inputs, see: https://axios-http.com/docs/req_config
Deploy to Render ```yaml -- name: 'Render Deploy' +- name: 'Render Deploy Image' uses: cssnr/web-request-action@v1 with: url: ${{ secrets.RENDER_HOOK }} @@ -196,8 +230,14 @@ For more information on inputs, see: https://axios-http.com/docs/req_config with: url: https://httpbin.org/post data: '{"key": "value"}' + data: | + '{"key": "value"}' + data: | + key: value ``` +Note: All data keys are identical as exemplar formats. +
Send File @@ -208,8 +248,24 @@ For more information on inputs, see: https://axios-http.com/docs/req_config url: https://httpbin.org/post file: path/to/file.txt name: file # Default - name of file key + filename: name.txt # Optional - file name ``` +
+
Set Axios Config + +```yaml +- name: 'Web Request' + uses: cssnr/web-request-action@v1 + with: + url: https://httpbin.org/post + config: | + timeout: 1000 + maxContentLength: 2000 +``` + +Reference: https://axios-http.com/docs/req_config +
All Inputs @@ -226,11 +282,14 @@ For more information on inputs, see: https://axios-http.com/docs/req_config { "key": "value" } + config: | + timeout: 5000 username: ${{ secrets.USERNAME }} password: ${{ secrets.PASSWORD }} insecure: false file: path/to/file.txt name: file + filename: name.txt ```
diff --git a/action.yml b/action.yml index 2b8229d..7e6a8ac 100644 --- a/action.yml +++ b/action.yml @@ -1,5 +1,5 @@ name: "Web Request" -description: "Send Web Requests like POST and GET with Axios using GitHub Actions." +description: "Easily Make Web Requests like GET/POST, Trigger Webhooks, and Send Files using Axios. Data inputs supports YAML/JSON format." author: "Shane" branding: icon: "globe" @@ -13,11 +13,13 @@ inputs: description: "Request Method" default: "POST" data: - description: "Request Data JSON" + description: "Request Data JSON/YAML" headers: - description: "Request Headers JSON" + description: "Request Headers JSON/YAML" params: - description: "Request Parameters JSON" + description: "Request Parameters JSON/YAML" + config: + description: "Axios Config JSON/YAML" username: description: "Basic Auth Username" password: @@ -30,6 +32,8 @@ inputs: name: description: "File Key Name" default: "file" + filename: + description: "Custom File Name" outputs: status: @@ -38,6 +42,8 @@ outputs: description: "Response Headers" data: description: "Response Data" + #url: + # description: "Response URL" runs: using: "node24" diff --git a/dist/index.js b/dist/index.js index 261a81b..1552ec2 100644 --- a/dist/index.js +++ b/dist/index.js @@ -34475,6 +34475,22 @@ module.exports = require("node:events"); /***/ }), +/***/ 3024: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:fs"); + +/***/ }), + +/***/ 4708: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:https"); + +/***/ }), + /***/ 7075: /***/ ((module) => { @@ -41533,14 +41549,21 @@ module.exports = /*#__PURE__*/JSON.parse('{"application/1d-interleaved-parityfec /******/ /************************************************************************/ var __webpack_exports__ = {}; +const fs = __nccwpck_require__(3024) +const https = __nccwpck_require__(4708) + const core = __nccwpck_require__(7484) + const axios = __nccwpck_require__(7269) const src_FormData = __nccwpck_require__(6454) -const fs = __nccwpck_require__(9896) -const https = __nccwpck_require__(5692) const yaml = __nccwpck_require__(4281) async function main() { + const version = process.env.GITHUB_ACTION_REF + ? `${process.env.GITHUB_ACTION_REF}` + : 'Source' + core.info(`🏳️ Starting Web Request Action - \u001b[35;1m${version}`) + // Inputs core.startGroup('Inputs') const url = core.getInput('url', { required: true }) @@ -41553,6 +41576,8 @@ async function main() { console.log('headers:', headers) const params = parseData('params') console.log('params:', params) + let config = parseData('config') + console.log('config:', config) const username = core.getInput('username') console.log('username:', username) const password = core.getInput('password') @@ -41563,44 +41588,51 @@ async function main() { console.log('file:', file) const name = core.getInput('name') console.log('name:', name) + const filename = core.getInput('filename') + console.log('filename:', filename) core.endGroup() // Inputs // Options - const auth = username && password ? { username, password } : {} - console.log('auth:', auth) + core.startGroup('Options') const httpsAgent = insecure ? new https.Agent({ rejectUnauthorized: false, }) : null console.log('httpsAgent:', httpsAgent) + const auth = username || password ? { username, password } : {} + console.log('auth:', auth) + const options = filename ? { filename } : {} + console.log('options:', options) + core.endGroup() // Options // File if (file) { + core.info('🔁 Converting Data to FormData') const form = new src_FormData() for (const [key, value] of Object.entries(data)) { form.append(key, value) } - form.append(name, fs.createReadStream(file)) + core.debug(`Adding file: ${file}`) + form.append(name, fs.createReadStream(file), options) Object.assign(headers, form.getHeaders()) data = form } - // Request - const config = { - url, - method, - headers, - params, - data, - auth, - httpsAgent, - } + // Config + config = { url, method, headers, params, data, auth, httpsAgent, ...config } + core.startGroup('Config') console.log('config:', config) + core.endGroup() // Config + + // Request + core.info('⌛ Processing Request') const response = await axios.request(config) console.log('response.status:', response.status) // console.log('response:', response) + // console.log('responseUrl:', response.request?.res?.responseUrl) // console.log('response.request._headers:', response.request._headers) + core.startGroup('Headers') console.log('response.headers:', response.headers) core.endGroup() // Headers @@ -41610,11 +41642,13 @@ async function main() { core.endGroup() // Data // Outputs + core.info('📩 Setting Outputs') core.setOutput('status', response.status) core.setOutput('headers', response.headers) core.setOutput('data', response.data) + // core.setOutput('url', response.request?.res?.responseUrl || '') - core.info(`\u001b[32;1mFinished Success`) + core.info(`✅ \u001b[32;1mFinished Success`) } /** diff --git a/src/index.js b/src/index.js index a35a436..5564350 100644 --- a/src/index.js +++ b/src/index.js @@ -1,11 +1,18 @@ +const fs = require('node:fs') +const https = require('node:https') + const core = require('@actions/core') + const axios = require('axios') const FormData = require('form-data') -const fs = require('fs') -const https = require('https') const yaml = require('js-yaml') async function main() { + const version = process.env.GITHUB_ACTION_REF + ? `${process.env.GITHUB_ACTION_REF}` + : 'Source' + core.info(`🏳️ Starting Web Request Action - \u001b[35;1m${version}`) + // Inputs core.startGroup('Inputs') const url = core.getInput('url', { required: true }) @@ -18,6 +25,8 @@ async function main() { console.log('headers:', headers) const params = parseData('params') console.log('params:', params) + let config = parseData('config') + console.log('config:', config) const username = core.getInput('username') console.log('username:', username) const password = core.getInput('password') @@ -28,44 +37,51 @@ async function main() { console.log('file:', file) const name = core.getInput('name') console.log('name:', name) + const filename = core.getInput('filename') + console.log('filename:', filename) core.endGroup() // Inputs // Options - const auth = username && password ? { username, password } : {} - console.log('auth:', auth) + core.startGroup('Options') const httpsAgent = insecure ? new https.Agent({ rejectUnauthorized: false, }) : null console.log('httpsAgent:', httpsAgent) + const auth = username || password ? { username, password } : {} + console.log('auth:', auth) + const options = filename ? { filename } : {} + console.log('options:', options) + core.endGroup() // Options // File if (file) { + core.info('🔁 Converting Data to FormData') const form = new FormData() for (const [key, value] of Object.entries(data)) { form.append(key, value) } - form.append(name, fs.createReadStream(file)) + core.debug(`Adding file: ${file}`) + form.append(name, fs.createReadStream(file), options) Object.assign(headers, form.getHeaders()) data = form } - // Request - const config = { - url, - method, - headers, - params, - data, - auth, - httpsAgent, - } + // Config + config = { url, method, headers, params, data, auth, httpsAgent, ...config } + core.startGroup('Config') console.log('config:', config) + core.endGroup() // Config + + // Request + core.info('⌛ Processing Request') const response = await axios.request(config) console.log('response.status:', response.status) // console.log('response:', response) + // console.log('responseUrl:', response.request?.res?.responseUrl) // console.log('response.request._headers:', response.request._headers) + core.startGroup('Headers') console.log('response.headers:', response.headers) core.endGroup() // Headers @@ -75,11 +91,13 @@ async function main() { core.endGroup() // Data // Outputs + core.info('📩 Setting Outputs') core.setOutput('status', response.status) core.setOutput('headers', response.headers) core.setOutput('data', response.data) + // core.setOutput('url', response.request?.res?.responseUrl || '') - core.info(`\u001b[32;1mFinished Success`) + core.info(`✅ \u001b[32;1mFinished Success`) } /**