-
Notifications
You must be signed in to change notification settings - Fork 144
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(environments): allowed-times constraint #586
Conversation
| { | ||
| "hours": "6-18", | ||
| "days": "Monday-Friday", | ||
| "tz": "America/Los_Angeles" |
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 the YAML payload have the tz
field as well?
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.
Yeah, we discussed this previously. It's necessary otherwise the server is going to interpret those hours as UTC
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.
No, it's an optional field.
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.
Also note the dynamic config to set a default TZ for windows other than UTC. This will be set to "America/Los_Angeles " so our users will only have to specify a TZ if they want something else.
@@ -6,6 +6,24 @@ import com.netflix.spinnaker.keel.api.DeliveryConfig | |||
|
|||
interface ConstraintEvaluator<T : Constraint> { | |||
|
|||
companion object { | |||
inline fun <reified T> getConstraintForEnvironment( |
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.
Kotlin newbie question: does this need to be reified
if you're not checking/using the type of T in the method?
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.
Correct. Good spot.
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.
Kotlin studies paying off! 😄
@JsonDeserialize(`as` = DependsOnConstraint::class) | ||
sealed class Constraint | ||
@JsonTypeInfo( | ||
include = JsonTypeInfo.As.PROPERTY, |
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.
This should be EXISTING_PROPERTY
Type(value = DependsOnConstraint::class, name = "depends-on"), | ||
Type(value = TimeWindowConstraint::class, name = "time-window") |
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.
You should be able to omit the name
since it's a constant in the types
| "windows": [ | ||
| { | ||
| "hours": "6-18", | ||
| "days": "Monday-Friday", |
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.
With this format can I specify something like "Monday, Wednesday and Friday"?
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.
Or is that just 3 separate windows?
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.
"and" isn't valid but you could specify "Monday, Wednesday, Friday"
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.
you can do a list of ranges/individual days: https://github.com/spinnaker/keel/pull/586/files#diff-cb20a8e71cc1d6a198f43860b58d5696R149
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.
@asher jinx.
| { | ||
| "hours": "6-18", | ||
| "days": "Monday-Friday", | ||
| "tz": "America/Los_Angeles" |
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.
Yeah, we discussed this previously. It's necessary otherwise the server is going to interpret those hours as UTC
import java.util.TimeZone | ||
import java.util.TimeZone.getTimeZone |
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.
Shouldn't we use java.time.ZoneId
here?
import java.util.TimeZone | ||
import java.util.TimeZone.getTimeZone | ||
|
||
/** |
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.
Thanks for taking the time to document the class so well. ❤️
.toString() | ||
.toLowerCase() | ||
val todayShort = now.dayOfWeek | ||
.getDisplayName(TextStyle.SHORT, Locale.getDefault()) |
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.
Shouldn't this be Locale.US
for consistency with the formatters up above?
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 it's the other way around, they should be Locale.default. Thanks for pointing out the discrepancy.
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.
💯 This is some serious awesomeness. Nice work!
I had a couple specific comments, but also a more general observation: the way the constraint syntax works right now, you can't tell from just reading it whether the time windows are inclusion windows (only deploy during these times) or exclusion windows (don't deploy during these times). You can try to make assumptions (e.g. "it's a constraint so it's probably asserting things to approve, not deny"), but there's definitely room for interpretation as-is.
I sort of think about these constraints like unit test assertions — in theory you should be able to read them and get a sense of what the plain-english intent is, when possible. Any thoughts on how we can make the expected behavior more clear? Perhaps naming the windows
field something more descriptive?
* constraints: | ||
* - type: time-window | ||
* windows: | ||
* - days: Monday,Tuesday-Friday |
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.
Up in a test file I saw all lowercase for this value — looks like in this evaluator we're coercing any capitalization into lowercase?
I don't have a problem with that necessarily, but might be good to shoot for a standardized way of doing examples.
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.
Or what if I add an additional window here that uses lower case? I want to show the flexibility in how days can be specified, but not sure how best to do that.
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.
yeah maybe? I dunno. I suspect all these constraints will need really thorough docs to be successful. It's a detail and probably not super important tbh.
* hours: 11-16 | ||
* tz: America/Los_Angeles | ||
* - days: Wed | ||
* hours: 12-14 |
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.
Any opinions on 24hr time vs 12pm-2pm
? Latter seems a little more readable, but not sure whether it's "better" from an overall perspective.
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.
next you'll want us to use Fahrenheit
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.
Kelvin, obviously.
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.
Am and pm, how very quaint.
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'm delighted with 24hour
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.
Alright alright, if everybody prefers 24h then let’s go with that. I’m coming from the perspective that for really any UI we and other engineers use day to day, times get mapped to 12h because that’s typically easier to visually parse at a glance. I generally see these YAML configs as more similar in their UX needs to UIs vs. actual programming code.
?: shortDayFormatter.parseUnresolved(days[1], ParsePosition(0)) | ||
?: throw InvalidConstraintException("Failed parsing day range $this") | ||
|
||
val intRange = IntRange(DayOfWeek.from(day1).value, DayOfWeek.from(day2).value) |
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.
What would happen here if you specify something backwards like thu-mon
?
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.
Probably not what the user would want.
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 we throw an InvalidConstraintException
in that case, then?
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.
Updated the behavior so wrap around hours and days work as one would reasonably expect.
name = "prod", | ||
constraints = setOf(constraint) | ||
) | ||
private val previousEnvironment = Environment( |
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.
Philosophical question for the group: I thought time-window
would be a type of constraint/requirement that would gate deployment into an environment regardless of a promotion flow between environments (I remember discussing this with Michael for the MD blog post), but the function in the evaluator interface is called canPromote
. Can this be used with a single environment?
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 you should be able to control deployment into any environment with a time-window
constraint. That applies if you have only 1 environment and you only want new builds deployed there on, let's say, weekdays.
I think that each constraint that is on an environment is evaluated, and they are && together. So if every constraint returns true
to canPromote()
then we would promote the artifact.
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.
Thanks, Emily. I was just wondering if for the tests for this particular constraint (unlike for depends-on
, which obviously needs 2 environments), we should then have a single environment in the fixtures.
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'll delete "staging" from the fixture if its causing confusion.
| - type: time-window | ||
| windows: | ||
| - hours: 6-18 | ||
| - days: mon-fri |
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 this yaml be:
windows:
- hours: 6-18
days: mon-fri
(no dash by days) to mean that those go together as one window?
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.
Ooh yes, otherwise it will be 2 separate windows
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.
good catch, I'm so bad at yaml!
Another thought: the execution window UI for pipelines has three shortcut links that auto-select days for you: Do any of those three seem like they'd be useful additions in the |
@erikmunson I'll add the weekdays/weekends shortcuts for days, good idea. (All is the default when days are omitted) |
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.
Other than the tiny things pointed out already, this looks great to me! Good stuff @asher!
81507a0
to
4f5b9f9
Compare
data class TimeWindow( | ||
val days: String? = null, | ||
val hours: String? = 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.
I think defining these here as strings means we get no validation when you submit a delivery-config. Do you think we could do some sort of validation like we do here, where that validation is pulled from the AllowedTimesConstraintEvaluator
?
I'd like to know when I submit that I've spelled something wrong vs an error later :)
import java.lang.RuntimeException | ||
|
||
class InvalidConstraintException( | ||
message: String |
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.
Probably should add constraint name here, so we know where the error is coming from?
class InvalidConstraintException(
constraintName: String,
message: String
) : RuntimeException("$constraintName: $message")
or something?
} | ||
.failed() | ||
.isA<InvalidConstraintException>() | ||
} |
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 like a test for when timezone is not set
* hours: 12-14 | ||
* tz: America/Los_Angeles | ||
*/ | ||
@Component |
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.
This class is super clear. Thanks for adding great class docs, useful helper functions, keeping the core logic short and easy to reason about, and pulling the complex parsing out into smaller functions.
I struggled with this kind of calculation in Swabbie, and it's delightful to see something that reads well and makes sense here!
No description provided.