Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into jw/update-prerelease-…
Browse files Browse the repository at this point in the history
…10.0
  • Loading branch information
justinwilaby committed Nov 4, 2024
2 parents 054835b + 80ab352 commit 4cdaf34
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 29 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/create-cli-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ on:
default: false

jobs:
check-for-moratorium:
if: fromJSON(inputs.isStableCandidate)
uses: ./.github/workflows/ctc.yml

get-version-channel:
runs-on: ubuntu-latest
outputs:
Expand Down
61 changes: 33 additions & 28 deletions .github/workflows/ctc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,59 @@ on:
workflow_call:

jobs:
create-ctc-lock:
# Use TPS service to get clearance to release from Salesforce Change-Traffic-Control:
# https://github.com/heroku/tps/blob/master/docs/ctc.md
check-tps-for-lock:
runs-on: ubuntu-latest
environment: ChangeManagement
steps:
- uses: actions/checkout@v4
- name: Call CTC API TO Create Lock
id: create-lock
- name: Check tps.heroku.tools for lock
id: check-lock
run: |
CODE=`curl --w '%{http_code}' \
-X PUT \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Token ${{ secrets.TPS_API_TOKEN_PARAM }}" \
-d '{"lock": {"sha": "${{ github.sha }}", "component_name": "${{ secrets.TPS_API_APP_ID }}"}}' \
${{ secrets.TPS_API_URL_PARAM }}/api/ctc`
echo "STATUS_CODE=$CODE" >> $GITHUB_ENV
echo "Response status code is $CODE."
STATUS_CODE="$(curl --w '%{http_code}' \
-X PUT \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Token ${{ secrets.TPS_API_TOKEN_PARAM }}" \
-d '{"lock": {"sha": "${{ github.sha }}", "component_slug": "cli"}}' \
https://tps.heroku.tools/api/ctc)"
echo "Response status code is $STATUS_CODE."
echo "STATUS_CODE=$STATUS_CODE" >> $GITHUB_ENV
- name: Retry if TPS returns 409
- name: Retry if lock cannot be obtained
id: retry-lock
env:
RETRY_LATER: "409"
uses:
nick-fields/retry@v2
nick-fields/retry@v3
if: ${{ env.STATUS_CODE == env.RETRY_LATER}}
with:
max_attempts: 15
max_attempts: 12
warning_on_retry: true
retry_wait_seconds: 3600
retry_wait_seconds: 300
retry_on_exit_code: 1
timeout_minutes: 3600
timeout_seconds: 60
command: |
CODE=`curl --w '%{http_code}' \
-X PUT \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Token ${{ secrets.TPS_API_TOKEN_PARAM }}" \
-d '{"lock": {"sha": "${{ github.sha }}", "component_name": "${{ secrets.TPS_API_APP_ID }}"}}' \
${{ secrets.TPS_API_URL_PARAM }}/api/ctc`
STATUS_CODE="$(curl --w '%{http_code}' \
-X PUT \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: Token ${{ secrets.TPS_API_TOKEN_PARAM }}" \
-d '{"lock": {"sha": "${{ github.sha }}", "component_slug": "cli"}}' \
https://tps.heroku.tools/api/ctc)"
echo "Response status code is $CODE"
if [ $CODE == "409" ]
echo "Response status code is $STATUS_CODE"
if [ $STATUS_CODE == "409" ]
then
exit 1
else
echo "STATUS_CODE=$CODE" >> $GITHUB_ENV
echo "STATUS_CODE=$STATUS_CODE" >> $GITHUB_ENV
fi
- name: Verify CTC Lock Did Not Fail for Other Reasons
- name: Verify lock status
id: verify-lock
env:
UPDATE_LOCK_SUCCESS: "200"
NEW_LOCK_SUCCESS: "201"
Expand Down
1 change: 1 addition & 0 deletions cspell-dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ searchpath
secrettoken
segv
selfsigned
sfdc
shellescape
showenvs
sigints
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@
"./lib/hooks/init/terms-of-service",
"./lib/hooks/init/performance_analytics"
],
"plugins:preinstall": [
"./lib/hooks/plugins/preinstall/disclaimers"
],
"prerun": [
"./lib/hooks/prerun/analytics"
],
Expand Down
30 changes: 30 additions & 0 deletions packages/cli/src/hooks/plugins/preinstall/disclaimers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {Hook, ux} from '@oclif/core'

const hook: Hook<'plugins:preinstall'> = async function (options) {
const npmPackageNames = ['@heroku/plugin-ai', '@heroku-cli/plugin-ai']

if (options.plugin.type !== 'npm' || !npmPackageNames.includes(options.plugin.name)) return

ux.warn(
'\n\nThis pilot feature is a Beta Service. You may opt to try such Beta Service in your sole discretion. ' +
'Any use of the Beta Service is subject to the applicable Beta Services Terms provided at ' +
'https://www.salesforce.com/company/legal/customer-agreements/. While use of the pilot feature itself is free, ' +
'to the extent such use consumes a generally available Service, you may be charged for that consumption as set ' +
'forth in the Documentation. Your continued use of this pilot feature constitutes your acceptance of the foregoing.\n\n' +
'For clarity and without limitation, the various third-party machine learning and generative artificial intelligence ' +
'(AI) models and applications (each a “Platform”) integrated with the Beta Service are Non-SFDC Applications, ' +
'as that term is defined in the Beta Services Terms. Note that these third-party Platforms include features that use ' +
'generative AI technology. Due to the nature of generative AI, the output that a Platform generates may be ' +
'unpredictable, and may include inaccurate or harmful responses. Before using any generative AI output, Customer is ' +
'solely responsible for reviewing the output for accuracy, safety, and compliance with applicable laws and third-party ' +
'acceptable use policies. In addition, Customer’s use of each Platform may be subject to the Platform’s own terms and ' +
'conditions, compliance with which Customer is solely responsible.\n',
)

const response = await ux.prompt('Continue? (Y/N)')
if (response.toUpperCase() !== 'Y') {
ux.error('Canceled', {exit: 1})
}
}

export default hook
129 changes: 129 additions & 0 deletions packages/cli/test/unit/hooks/disclaimers.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import {Config, ux} from '@oclif/core'
import {CLIError} from '@oclif/core/lib/errors'
import {expect} from 'chai'
import {join} from 'path'
import * as sinon from 'sinon'
import {stderr} from 'stdout-stderr'

describe('disclaimers ‘plugins:preinstall’ hook', function () {
let config: Config
let sandbox: sinon.SinonSandbox

before(async function () {
config = await Config.load({root: join(__dirname, '../../..')})
sandbox = sinon.createSandbox()
})

afterEach(function () {
sandbox.restore()
})

context('when installing from a Github repository', function () {
it('doesn’t show the disclaimer', async function () {
stderr.start()
config.runHook('plugins:preinstall', {
plugin: {
url: 'https://github.com/heroku/heroku-api-plugin',
type: 'repo',
},
})
stderr.stop()

expect(stderr.output).not.to.include('This pilot feature is a Beta Service.')
})
})

context('when installing a plugin different from ‘@heroku/plugin-ai’ or ‘@heroku-cli/plugin-ai’', function () {
it('doesn’t show the disclaimer', async function () {
stderr.start()
config.runHook('plugins:preinstall', {
plugin: {
name: '@heroku-cli/plugin-events',
tag: 'latest',
type: 'npm',
},
})
stderr.stop()

expect(stderr.output).not.to.include('This pilot feature is a Beta Service.')
})
})

context('when installing the ‘@heroku/plugin-ai’ plugin', function () {
it('shows the disclaimer and prompts the user', async function () {
const promptStub = sandbox.stub(ux, 'prompt').onFirstCall().resolves('y')

stderr.start()
await config.runHook('plugins:preinstall', {
plugin: {
name: '@heroku/plugin-ai',
tag: 'latest',
type: 'npm',
},
})
stderr.stop()

expect(stderr.output).to.include('This pilot feature is a Beta Service.')
expect(promptStub.calledOnce).to.be.true
})

it('cancels installation if customer doesn’t accepts the prompt', async function () {
sandbox.stub(ux, 'prompt').onFirstCall().resolves('n')

stderr.start()
try {
await config.runHook('plugins:preinstall', {
plugin: {
name: '@heroku/plugin-ai',
tag: 'latest',
type: 'npm',
},
})
} catch (error: unknown) {
stderr.stop()
const {message, oclif} = error as CLIError
expect(message).to.equal('Canceled')
expect(oclif.exit).to.equal(1)
}
})
})

context('when installing the ‘@heroku-cli/plugin-ai’ plugin', function () {
it('shows the disclaimer and prompts the user', async function () {
const promptStub = sandbox.stub(ux, 'prompt').onFirstCall().resolves('y')

stderr.start()
await config.runHook('plugins:preinstall', {
plugin: {
name: '@heroku-cli/plugin-ai',
tag: 'latest',
type: 'npm',
},
})
stderr.stop()

expect(stderr.output).to.include('This pilot feature is a Beta Service.')
expect(promptStub.calledOnce).to.be.true
})

it('cancels installation if customer doesn’t accepts the prompt', async function () {
sandbox.stub(ux, 'prompt').onFirstCall().resolves('n')

stderr.start()
try {
await config.runHook('plugins:preinstall', {
plugin: {
name: '@heroku-cli/plugin-ai',
tag: 'latest',
type: 'npm',
},
})
} catch (error: unknown) {
stderr.stop()
const {message, oclif} = error as CLIError
expect(message).to.equal('Canceled')
expect(oclif.exit).to.equal(1)
}
})
})
})
2 changes: 1 addition & 1 deletion scripts/postrelease/change_management
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async function sendDeployNotification () {
release: {
actor_email: actorEmail,
app_id: appId,
component_name: 'cli',
component_slug: 'cli',
description: `Deploy ${sha} of heroku/cli in ${stage}`,
sha,
stage
Expand Down

0 comments on commit 4cdaf34

Please sign in to comment.