-
Notifications
You must be signed in to change notification settings - Fork 84
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
apb pkg public interface overhaul #773
Conversation
Introduces the Executor as well.
Binding -> Bind Unbinding -> Unbind
The New funcs really are not offering much.
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 really like this, great improvement. I'll look more deeply at it again but so far. +1
pkg/apb/bind.go
Outdated
metrics.ActionStarted("bind") | ||
executionContext, err := e.executeApb( | ||
"bind", instance.Spec, instance.Context, parameters) | ||
defer runtime.Provider.DestroySandbox( |
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 do the defer AFTER we kickoff the executeApb? feels like we should do this before. Because the err we are looking at on line 53 is related to the execute not the defer.
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 not clear on the best practice with defers. If I'm doing something and I know I'll need to call the relevant "finish" function at the end of it, it makes sense to me to defer the call immediately after, assuming the mechanics of defer apply. A good example of this is the metrics hooks in the apbJob
, I just partner the Start with a defered finish right way. I'll take a closer look at this, thanks for raising it.
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.
After looking at this, I think it doesn't actually matter in practice? Regardless of where the defer call is made, it will execute at the conclusion of the function. Optically, it might make sense to be made elsewhere, though.
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 that if the sandbox is created in the executeApb method then calling defer afterwards makes sense. It is how the code looks currently I believe?
Speaking of Optics and readability as we have a call to runtime.Provider.DestroySandbox
would it be reasonable to extract the call to the CreateSandbox method from the executeApb method.
runtime.Provider.CreateSandbox()
defer runtime.Provider.DestroySandbox()
e.executeApb()
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.
It doesn't matter, what I was saying was tying statements together.
go func() {
e.start()
metrics.ActionStarted("bind")
defer runtime.Provider.DestroySandbox(
executionContext.PodName,
executionContext.Namespace,
executionContext.Targets,
clusterConfig.Namespace,
clusterConfig.KeepNamespace,
clusterConfig.KeepNamespaceOnError,
)
executionContext, err := e.executeApb("bind", instance.Spec, instance.Context, parameters)
if err != nil {
log.Errorf("Problem executing apb [%s] bind", executionContext.PodName)
e.finishWithError(err)
return
}
So the err != nil
follows the executeApb so it is obvious that it is related and highly unlikely that someone will come and add another line of code that set err. As it stands now the defer statement separates the executeApb and the err check. If someone comes in later they could accidentally set err and we'll be eating an err.
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.
The issue is that executionContext
isn't in scope until the executeApb
call finished. I'll update to keep the err check tied closely.
I think it's actually exactly where it needs to be, we definitely still want to destroy the sandbox even if an error occurs, but it also must be after executionContext is in scope. (unless we implement @maleck13's suggestion, which I do think is clearer and maybe makes sense in another PR).
pkg/apb/executor.go
Outdated
return e.extractedCredentials | ||
} | ||
|
||
func (e *Executor) start() { |
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 would like to rename these so they are obvious. I kept having to lookup what it meant by start. I'd think something like sendStart
maybe it's just me. I'll think about it.
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.
They're really lifecycle hooks that need to be called from the action methods to alert the Executor
to the current state of what's going on (and it abstracts the fact that messages are being sent). I originally named updateDescriptor
to something like sendDescriptorMessage
, but I felt like the latter was a less abstract name. I want to call high level hooks to alert the executor to triggers in the cycle without saying, "hey send this message", because it's better if the actions don't even care about that. They just say "hey, this thing happened, do what you would like with this info".
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 also say that I tried to avoid imposing additional requirements on callers since I expect these to turn into the primary methods that people will use with libapb
. The worst of it is to ask folks to instantiate an Executor
with NewExecutor
; I've personally seen us on this path for a while, so I'm happy to finally get this down on paper, so to speak.
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 the use of the word start
on a type called Executor
is a little confusing as it seems to suggest this is how you start execution.
Off the cuff, but what if you were to have a internal statusPublisher
object that had these methods so that it would read as e.statusPublisher.start()
also would give an opportunity for this to be an interface that could be overridden for tests or other uses if we exposed it via a constructor.
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 think there is a better name for Executor
? I'm still hesitant to name these something like "sendX" or other related messaging concept. They're internal lifecycle hooks intended to be called by the internal APB code, and do more than send a message over the buffer (not much today, but tomorrow they might).
also would give an opportunity for this to be an interface that could be overridden for tests or other uses if we exposed it via a constructor.
Definitely interested in this since decent test coverage is the biggest thing missing right now. I don't think callers should have to worry about injecting that via ctor though. A sensible default that can get overriden by test code I think makes sense, given this is an internal detail.
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.
Definitely interested in this since decent test coverage is the biggest thing missing right now. I don't think callers should have to worry about injecting that via ctor though. A sensible default that can get overriden by test code I think makes sense, given this is an internal detail.
Yep agree no need to expose as long as it can be overridden in test code
pkg/apb/executor.go
Outdated
context *Context, | ||
p *Parameters) (ExecutionContext, error) { | ||
// Executor - Main struct used for running APBs. Manages their lifecycle. | ||
type Executor struct { |
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 would like to make it more clear that a single Executor for a single APB run. At least that is how I am reading this code, am I wrong?
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 that's exactly correct. What do you think the best way is to convey 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.
I would say documentation for the end user. Code examples will help too. I think that nothing from the code will make it more obvious. At least I can't think of anything at the moment.
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 a pretty big PR. I have added quite a few thoughts and comments. If some don't make sense feel free to ignore, can be difficult to understand the full thought process from an external perspective.
pkg/apb/bind.go
Outdated
metrics.ActionStarted("bind") | ||
executionContext, err := e.executeApb( | ||
"bind", instance.Spec, instance.Context, parameters) | ||
defer runtime.Provider.DestroySandbox( |
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 that if the sandbox is created in the executeApb method then calling defer afterwards makes sense. It is how the code looks currently I believe?
Speaking of Optics and readability as we have a call to runtime.Provider.DestroySandbox
would it be reasonable to extract the call to the CreateSandbox method from the executeApb method.
runtime.Provider.CreateSandbox()
defer runtime.Provider.DestroySandbox()
e.executeApb()
pkg/apb/executor.go
Outdated
} | ||
|
||
// GetPodName - Returns the name of the pod running the APB | ||
func (e *Executor) GetPodName() 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.
nit: generally in go you don't use the Get
https://golang.org/doc/effective_go.html#Getters . It's really a small thing but if this is to turn into an external lib that we hope will be consumed by go developers probably worth following the idioms
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!
pkg/apb/executor.go
Outdated
status.State = StateFailed | ||
e.lastStatus = status | ||
e.statusChan <- status | ||
close(e.statusChan) |
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.
seeing the close in two different methods makes me a little nervous that we will potentially close the channel twice and cause a panic, maybe some form of protection against this would be useful
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.
Very good point. Reading a bit, it looks like there isn't a safe way to check if a channel has been closed without reading from it, which feels ugly and indirect to me. Alternatively, we can lock the sensitive code and nil it out, then check if it's still present before closing.
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 some locks.
pkg/apb/executor.go
Outdated
close(e.statusChan) | ||
} | ||
|
||
func (e *Executor) updateDescription(newDescription 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.
At some point we may want to be able to add the Dashboard URL, but probably a feature of its own rather than to do with this PR
pkg/apb/provision_or_update.go
Outdated
stateUpdates chan<- JobState) (string, *ExtractedCredentials, error) { | ||
|
||
func (e *Executor) provisionOrUpdate(method executionMethod, instance *ServiceInstance) { | ||
e.start() |
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.
Opinion: what if we were to have defer e.stop()
and remove the close from the other funcs? The other funcs would then become sendError()
and sendSuccess()
Another option here would be to return a function from start that closed the channel
stop := e.start()
defer stop()
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 see comment below around the start method
pkg/apb/executor.go
Outdated
return e.extractedCredentials | ||
} | ||
|
||
func (e *Executor) start() { |
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 the use of the word start
on a type called Executor
is a little confusing as it seems to suggest this is how you start execution.
Off the cuff, but what if you were to have a internal statusPublisher
object that had these methods so that it would read as e.statusPublisher.start()
also would give an opportunity for this to be an interface that could be overridden for tests or other uses if we exposed it via a constructor.
specID string | ||
bindingID *string | ||
method apb.JobMethod | ||
metricsJobStartHook metricsHookFn |
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.
So potentially as part of the work I have outlined under the proposal for the changes to the work engine these would become StateObservers / Subscribers and not needed here
efee7e1
to
1caffe1
Compare
pkg/broker/jobs.go
Outdated
) | ||
|
||
type metricsHookFn func() | ||
type runFn func(*apb.Executor) <-chan apb.StatusMessage |
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 means that we can delete the Provisioner, Binder all functions
} | ||
|
||
// Executor - Composite executor interface. | ||
type Executor interface { |
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.
👍
pkg/apb/types.go
Outdated
// Provisioner defines a function that knows how to provision an apb | ||
type Provisioner func(si *ServiceInstance, statusUpdate chan<- JobState) (string, *ExtractedCredentials, error) | ||
// Provisioner - a function that knows how to provision an apb | ||
type Provisioner func(si *ServiceInstance) (<-chan StatusMessage, error) |
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 can delete these interfaces with this PR?
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've actually been going back and forth on this. I deleted them and then force pushed them back in. I'm trying to test asb_jobs, and the runFn
should render these moot, so I'll probably pull them again.
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 don't see Provisioner used anywhere.
@shawn-hurley Are you taking issue with the I think you should be able to get |
No we are exposing how we pull creds from the pod in the ext_creds.go I
think.
If it’s out of scope just tell me to go away until your done :)
…On Mon, Feb 19, 2018 at 4:52 PM Erik Nelson ***@***.***> wrote:
@shawn-hurley <https://github.com/shawn-hurley> Are you taking issue with
the Executor.ExtractedCredentials? I went back and forth on this one...
originally I did not have an accessor because it's a pointer type and could
easily be mutated by a caller, but I added the accessor method for
consistency because it felt silly to have the rest of the fields retrieved
by method and not this one.
I think you should be able to get ExtractedCredentials from the Executor
somehow.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#773 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABza922jf5j9eqVs8QXpo4_OJ4Qg3J8Xks5tWe0BgaJpZM4SJ47R>
.
|
@shawn-hurley can you elaborate? This is all good discussion; we're going to have to figure out how to split up the changes so this refactoring doesn't apply to the whole world. I would really like to keep PRs small and focused, and I know I've expressed that to you. I'm completely violating that here. I'm also finding it super helpful to post some code and have you guys comment, and iterate. I often hit questions that I'm not comfortable taking a stance with on my own. |
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.
Looking great. Two things:
- verify this is good: e.extractedCredentials = creds, if so, proceed.
- question about the executor status methods
I'd be okay if the sync work was a separate PR.
pkg/apb/bind.go
Outdated
metrics.ActionStarted("bind") | ||
executionContext, err := e.executeApb( | ||
"bind", instance.Spec, instance.Context, parameters) | ||
defer runtime.Provider.DestroySandbox( |
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.
It doesn't matter, what I was saying was tying statements together.
go func() {
e.start()
metrics.ActionStarted("bind")
defer runtime.Provider.DestroySandbox(
executionContext.PodName,
executionContext.Namespace,
executionContext.Targets,
clusterConfig.Namespace,
clusterConfig.KeepNamespace,
clusterConfig.KeepNamespaceOnError,
)
executionContext, err := e.executeApb("bind", instance.Spec, instance.Context, parameters)
if err != nil {
log.Errorf("Problem executing apb [%s] bind", executionContext.PodName)
e.finishWithError(err)
return
}
So the err != nil
follows the executeApb so it is obvious that it is related and highly unlikely that someone will come and add another line of code that set err. As it stands now the defer statement separates the executeApb and the err check. If someone comes in later they could accidentally set err and we'll be eating an err.
pkg/apb/bind.go
Outdated
executionContext.Namespace, | ||
instance.Spec.Runtime, | ||
) | ||
e.extractedCredentials = creds |
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 if there is an err with ExtractCredentials
? Do we want to set e.extractedCredentials
before checking the err value? If this is a conscience decision then okay, just wanted to make sure we're not setting something invalid.
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.
It was actually originally written this way, I thought about it and the expectation is that creds will be nil if an error is returned from ExtractCredentials
. Your comment is probably safer, however. Will update.
pkg/apb/deprovision.go
Outdated
if err != nil { | ||
log.Errorf("Problem executing apb [%s] deprovision", executionContext.PodName) | ||
e.finishWithError(err) | ||
return |
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 comment from bind.go regarding defer separating the err check.
return provisionOrUpdate(executionMethodProvision, instance, stateUpdates) | ||
go e.provisionOrUpdate(executionMethodProvision, instance) | ||
|
||
return e.statusChan |
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.
👍 provision.go looks okay
// provisionOrUpdate as an implementation detail. | ||
return provisionOrUpdate(executionMethodUpdate, instance, statusUpdate) | ||
go e.provisionOrUpdate(executionMethodUpdate, instance) | ||
|
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.
update.go is okay
|
||
func watchPod( | ||
podName string, namespace string, podClient v1.PodInterface, | ||
updateDescription updateDescriptionFn, |
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.
watch_pod changes 👍
@@ -1241,7 +1242,7 @@ func (a AnsibleBroker) Update(instanceUUID uuid.UUID, req *UpdateRequest, async | |||
log.Debugf("toPlanName: [%s]", toPlan.Name) | |||
log.Debugf("PreviousValues: [ %+v ]", req.PreviousValues) | |||
log.Debugf("ServiceInstance Parameters: [%v]", *si.Parameters) | |||
ujob := NewUpdateJob(si, apb.Update) | |||
ujob := &UpdateJob{si} |
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 okay with using the job structs directly 👍
return e.Update(j.serviceInstance) | ||
}, | ||
} | ||
job.Run(token, msgBuffer) |
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 looked over the entire jobs.go. ❗ looks great. I like it.
// BindingJobStarted - Add a provision job to the counter. | ||
func BindingJobStarted() { | ||
// BindJobStarted - Add a provision job to the counter. | ||
func BindJobStarted() { | ||
defer recoverMetricPanic() |
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.
okay, this is fine.
pkg/apb/executor.go
Outdated
status := e.lastStatus | ||
status.State = StateInProgress | ||
e.lastStatus = status | ||
e.statusChan <- status |
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 confused by this, we copy the lastStatus into status, set its state, then set it back on lastStatus. Why isn't this just:
func (e *executor) start() {
log.Debug("executor::start")
e.lastStatus.State = StateInProgress
e.statusChan <- e.lastStatus
}
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 I was on autopilot here and it felt like a redux action, which is of course totally irrelevant here. I'll update 😄
@eriknelson re: extracted credentials This is what I was thinking about: https://github.com/openshift/ansible-service-broker/blob/master/pkg/apb/ext_creds.go#L48 I agree that this should be a follow-on, your right that this is something that can be done separately. I mentioned it here because #771 I thought was referring to the |
@shawn-hurley I actually had the same thought and had marked it private, but the broker actually calls it in broker.go as part of recovery, so I left it alone. |
I didn't realize that we may want to move recover out of the broker then. I think that may require a new issue to track and discuss, thoughts @jmrodri @rthallisey @eriknelson? |
@shawn-hurley IMO recover is firmly a concept in the broker domain. I'm not sure where else it would belong aside from there. |
We were previously sending a bunch of duplicate messages, now the jobs trust the status messages. This should fix ci issues.
* start -> actionStarted * finishWithSuccess -> actionFinishedWithSuccess * finishWithError -> actionFinishedWithError
Adds testify
@shawn-hurley @maleck13 @rthallisey Think this should be considered complete at this point. I'm planning on submitting another PR with the sync work. |
This utterly ballooned to be almost unreviewable after I added the vendor stuff. If you look at the individual commits prior to that, I added Gotta say, it was easy to integrate and incredibly useful. I would encourage us to continue with that pattern for mocking deps. |
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.
One other question that I asked in IRC because, it is for my edification and not something I want to block this PR.
@@ -0,0 +1,134 @@ | |||
// Code generated by mockery v1.0.0 |
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 am wondering if we should have a mock package for this?
Just makes it more clear in your import that you're importing a mock?
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 started thinking about this as well, but it implies each pkg would have a corresponding child mock package? Does that make sense?
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 if you were to create a top level mock package that contained shared mocks instead of a mock package in each individual package?
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 don't think we want to be mixing mocks for a bunch of different packages into one package.
Thank you for the reviews everyone! Merging since there is a bunch of work dependent on this, and it's green. |
* Initial commit moving things around in apb Introduces the Executor as well. * Remove individual jobs * Add consolidated jobs * Some minor modifications to apb func sigs * Update metrics names Binding -> Bind Unbinding -> Unbind * Use raw structs instead of New* variants The New funcs really are not offering much. * Mark non-bindable APBs finished * Update skipExection APBs with a completion desc * Fix lint errors * Protect statusChan closures with mutex * Use golang friendly getter names * Fix existing tests * Add initial jobs testing * Move to Executor interface for testing * Only set extractedCredentials with no err * Remove old apb run fns * Remove verbose status setting * Fix some of the job report logic We were previously sending a bunch of duplicate messages, now the jobs trust the status messages. This should fix ci issues. * Update executor hook names for more clarity * start -> actionStarted * finishWithSuccess -> actionFinishedWithSuccess * finishWithError -> actionFinishedWithError * Initial tests plus some fixes * Add mock executor and finish jobs_tests * Vendor updates Adds testify * Remove debug var
Describe what this PR does and why we need it:
CC: @maleck13, this is the intended follow-up for #619. We identified the way we'd like the public interfaces of the apb pkg to move in light of trying to think more consciously about what the experience should be like consuming
libapb
. Namely:StatusMessage
channel.Changes proposed in this pull request:
Executor
type that manages the APB lifecycle, and exposes the various methods for execution.apbJob
type, with concrete wrapper methods that call into the generic job.Couple blockers before a merge:
I deleted a lot of non-relevant tests and haven't added any to test the new code paths, so I think we need a strategy to make sure the new code is properly tested and to get that implemented. I mostly wanted to just prove out in the code that this was possibleThe new jobs routine is fully tested.EDIT 02/20: I added white box testing that exercises the new job branches. I'm not too concerned with this now given a green
make ci
+make check
.TODO: I still need to implement theThis is required, but the domain of another PR.Sync
variants of the apb actions. I'm seeing them simply call the async versions and block while watching the channel. The caller will know no better, and we're using the same codepaths.Which issue this PR fixes (This will close that issue when PR gets merged)
fixes #771