-
Notifications
You must be signed in to change notification settings - Fork 186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(cli): Introduce zapier canary command #861
Conversation
Nice this looks so promising! And great turnaround |
Ah gotcha, yeah I like the subcommands better! I'll swap those in instead |
@standielpls just updated this to use subcommands so now we have |
duration: flags.integer({char: 'd', description: 'Duration of the canary in seconds', required: true}), | ||
}, | ||
opts: { | ||
format: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed, thanks for catching that - it was a remnant from using a table for formatting
} | ||
|
||
validateDuration(duration) { | ||
if (!duration || duration <= 30 || duration > 24 * 60 * 60) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want duration < 30
versionFrom: flags.string({char: 'f', description: 'Version to route traffic from', required: true}), | ||
versionTo: flags.string({char: 't', description: 'Version to canary traffic to', required: true}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move the from/to versions as "arguments" - these are things the command should act on. (migrate example)
Percent i think it's closer to a required argument as well, since this should be set by the user, I don't want to default to anything.
Flags are treated more like options, and are things we can use to alter the behaviour (like duration).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I'd initially put them in flags because I thought it was little more descriptive, but I see what you mean. I moved everything to args
Do we want duration
to be a flag though? Seems like a required arg to me, I'd prefer the user to be explicit about what they're wanting to do
return activeCanaries.objects.find(c => c.from_version === versionFrom && c.to_version === versionTo) || null; | ||
} | ||
|
||
validateVersions(versionFrom, versionTo) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should check if versionFrom === versionTo
as well
} | ||
}); | ||
CanaryCreateCommand.description = | ||
'Create a new canary deployment, diverting a specified percentage of traffic from one version to another for a specified duration.' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be a little bit more explicit here so it is clear to users what is going on.
Create a new canary deployment, diverting a specified percentage of traffic from one version to another for a specified duration.
Only one canary can be active at the same time. You can run `zapier canary:list` to check. If you would like to create a new canary with different parameters, you can wait for the canary to finish, or delete it using `zapier canary:delete X Y`.
Note: this is similar to `zapier migrate` but different in that this is temporary and will "revert" the changes once the specified duration is expired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 I want to say something about breaking changes as well (1.X.Y to 2.X.Y) and advise against it, or to block it from letting the user do it entirely, what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I incorporated your longer description, thank you!
Re: the breaking changes, I added a similar line from the migration command: **Only use this command to canary traffic between non-breaking versions!**
Here's what I was thinking. The endpoint for canary currently has a TODO to check for breaking changes before allowing a canary.
So I thought for now (for this v1) we could skip that check and simply warn the user not to do it. Then as part of v2, add the check to the API and do something more specific with the result here in the CLI. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good to me
const existingCanary = await this.findExistingCanary(versionFrom, versionTo); | ||
if (existingCanary) { | ||
const secondsRemaining = existingCanary.until_timestamp - Math.floor(Date.now() / 1000); | ||
this.log(`A canary deployment already exists from version ${versionFrom} to version ${versionTo}, there are ${secondsRemaining} seconds remaining`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a helper in there?
If you would like to stop this canary now, run `zapier canary:delete {versionFrom} {versionTo}`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added 👍
} | ||
} | ||
|
||
CanaryDeleteCommand.flags = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to create, can we make these into arguments
return; | ||
} | ||
|
||
const confirmed = await this.confirm(`Are you sure you want to delete the canary from ${versionFrom} to ${versionTo}?`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice
|
||
async findExistingCanary(versionFrom, versionTo) { | ||
const activeCanaries = await listCanaries(); | ||
return activeCanaries.objects.find(c => c.from_version === versionFrom && c.to_version === versionTo) || null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need || null
isn't .find() falsy if not found already ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, removed
return activeCanaries.objects.find(c => c.from_version === versionFrom && c.to_version === versionTo) || null; | ||
} | ||
|
||
validateVersions(versionFrom, versionTo) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same, check if version from and to are the same
if (activeCanaries.objects.length > 0) { | ||
const existingCanary = activeCanaries.objects[0]; | ||
const secondsRemaining = existingCanary.until_timestamp - Math.floor(Date.now() / 1000); | ||
this.log(`A canary deployment already exists from version ${existingCanary.from_version} to version ${existingCanary.to_version}, there are ${secondsRemaining} seconds remaining. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@standielpls one thing I'll call out here - since the API currently only allows one canary at a time, I've changed this code to block creation of a new one if any canary exists. Initially it checked for a canary matching the versionTo
and versionFrom
args, but I think this is simpler and offers less potential for confusion
So tldr, only one canary can exist at a time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also don't super love the activeCanaries.objects[0]
but the API returns the array right now, so 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NICE - just a couple more small things
'Create a new canary deployment, diverting a specified percentage of traffic from one version to another for a specified duration. \n' + | ||
'\n' + | ||
'Only one canary can be active at the same time. You can run `zapier canary:list` to check. If you would like to create a new canary with different parameters, you can wait for the canary to finish, or delete it using `zapier canary:delete a.b.c x.y.z`. \n' + | ||
'\n' + | ||
'Note: this is similar to `zapier migrate` but different in that this is temporary and will "revert" the changes once the specified duration is expired. \n' + | ||
'\n' + | ||
'**Only use this command to canary traffic between non-breaking versions!**'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use the same pattern as migrate and use string literals
CanaryCreateCommand.description = `Create a new canary deployment ....
You can run \`zapier canary:list\`
`
'zapier canary:create --versionFrom=1.0.0 --versionTo=1.1.0 --percent=25 --duration=720', | ||
'zapier canary:create -f 2.0.0 -t 2.1.0 -p 50 -d 300' | ||
] | ||
'zapier canary:create 1.0.0 1.1.0 25 720', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk if this is a problem or not, but the percent and duration numbers can get mixed up easily. It'd be bad if they wanted 100s of canary and 30% traffic, but it was in the wrong order, creating a canary that is 100% traffic for 30s.
So I think it'd be nice to either
- Show the summary upfront but ask for confirmation
- turn percent/duration into options (so it can be passed in like -p 25, -d 30 for example)
I'm fine with either option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved percent and duration both back to options - either --percent
or -p
will work and same for duration
I feel better about this now. The potential for confusion was bothering me a bit too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
woooo!
Adds a new command -
zapier canary
🐦This command enables users to divert a portion of traffic from one app version to a different app version for a specified amount of time.
Here's a gif of the command working...