Use Jenkins REST API to resume a paused pipeline? - jenkins

I have a Jenkins declarative pipeline with an input prompt.
stage('Approval') {
when {
branch "qa"
}
input {
message "Approve release?"
ok "Yes"
parameters {
string(name: 'IS_APPROVED', defaultValue: 'Yes', description: 'Approve?')
}
}
steps {
echo "Commit to master"
}
}
I have a 3rd party app that abstracts the use of Jenkins from business domain users. I want a button in the 3rd party app that when clicked, will approve the build for production release.
Is there a Jenkins REST API that I can call to provide the stage with input parameters and resume the build.

Disclaimer: IMHO, this feature is poorly documented. I figured most of this out from a bunch of SO questions with partial answers and several blog articles, and very little from actual Jenkins docs. However, it seems to work nicely on Jenkins 2.73.2.
First, I think you need to add an id attribute to your input.
Then, you can send a POST request to one of these:
http://yourjenkins/job/${YOUR_PROJECT}/${BUILD_NUMBER}/input/${INPUT_ID}/abort
This will cancel your job and ignore any parameter.
http://yourjenkins/job/${YOUR_PROJECT}/${BUILD_NUMBER}/input/${INPUT_ID}/proceedEmpty
This will resume your job and ignore any parameter.
http://yourjenkins/job/${YOUR_PROJECT}/${BUILD_NUMBER}/input/${INPUT_ID}/submit
This will resume your job, and you can send parameters. But:
You will need to send a proceed parameter with the caption of the 'Proceed' button.
You will need to send a json parameter with a URL-encoded JSON document with the form {"parameter":[{"name":"param1","value":"valueOfParam1"},{"name":"param2","value":"valueOfParam2"}]}, these will be your actual input parameters.
If you fail to send a valid json parameter, your job will continue anyway, it just won't get any parameter.
On success, this will return a '302 Found' and a redirection to user interface, which might interfere with your code and/or cause error handling issues.
http://yourjenkins/job/${YOUR_PROJECT}/${BUILD_NUMBER}/wfapi/inputSubmit
This seems to be the right way. You need to send inputId and json (see previous point). On success, this will return a '200 OK' with an empty response. You can also check out /wfapi and /wfapi/nextPendingInputAction on a paused job for more information.
Keep in mind that you will need to send authentication credentials and CSRF token for each request. Also, for the use case you describe, you probably wouldn't need parameters for your input, but just the proceed/abort built-in action.

Related

How to properly save updates to domain objects in Groovy/Grails

I'm starting to touch the Groovy/Grails backend of my organization and am tasked with updating the User on our Document domain object. The problem is, after hitting the update endpoint from the frontend with the correct params attached, the backend responds with an unchanged Document object.
Here is the code:
if (requestParams.userEmail) {
def contact = User.findByEmail(requestParams.userEmail)
log.debug('Reading user found by passed email contact={} error={}',contact, contact.errors.allErrors.inspect())
if (!contact) {
response.status = 400
render WebserviceError.badInput as JSON
return
}
document.user = contact
document.user.save(flush: true)
}
document.save(flush: true)
render survey as JSON
The frontend returns a promise and I'm logging the promise response, and it shows an unchanged Document object with the same exact user attached. I don't receive a 400 so it looks like the contact is successfully found.
I tried adding flush:true to the user.save call and the document.save call and that did not help.
Are there any obvious wrongdoings in my code?
Well db operations should be in a service, not in a controller, using #Transactional, preferably the gorm version not the spring version. You shouldn't need to use flush: true. Then fron the service you can return to the controller, andrender as JSON.
You don’t state that you see the debug statement on the server indicating a found user, perhaps it’s never actually getting to this section?
I assume that the code provided is incomplete, as we don’t see that the survey being returned contains the document that’s being updated. And also the braces look unbalanced, as if there’s a control flow issue. (i.e. why are there 2 opening braces but 3 closing braces?)
I’d suggest that you use a debugger on your code to see how control is actually flowing. Most Java IDEs support easy debugging, essentially clicking the debug button rather than the run button. Set a number of breakpoints sprinkled through this code to catch requests and call the API endpoint from your frontend.
is Document the parent? User a child?
User.addTodocument(someUser)
then Document.merge()

Get Email From Hunter.io user Zapier Code Step

I'm trying to use Zapier to call the Hunter.io API and return the first email. Since Zapier is pretty linear I need to be able to return individual emails. When I run this I get the following error "We had trouble sending your test through. Please try again. Error:
You must return a single object or array of objects." I realize I put my API key in there - its only linked to a free version so don't go wild :-).
Any help is appreciated.
fetch('https://api.hunter.io/v2/domain-search?domain=' + inputData.website + '&api_key=11b44ca200c3b3ac0b5cf08091bce3346acd2ed3')
  .then(function(res) {
    return res.json();
  })
  .then(function(json) {
    console.log(json);
    callback(null, json.emails);
  })
  .catch(callback);
David here, from the Zapier Platform team.
Luckily this is an easy fix - it looks like the json that comes back has top-level data and meta keys, the former of which has the emails, you're looking for. If you change your successful callback to
callback(null, json.data.emails)
you should be good to go. Note that subsequent steps will run for each email returned during each run of the zap and no deduplication happens. Make sure whatever's downstream of this can happen a lot without consequences!
Also you'll definitely want to regenerate your API Key once this is resolved. Definitely not something you want floating around. :)

How to remove Ephemeral Messages

I'm trying to figure out the mechanism to post an ephemeral message to a user and then remove it and replace it with a message visible to all. Similar behavior to giphy in which the Slash Command shows an interactive ephemeral message and creates a channel message once the user decides which gif to send. I'm also curious about updating the ephemeral message. I assume this can be done by the response_url if we use an interactive ephemeral message.
I initially figured I'd just create a ephemeral message using chat.postEphemeral and then call chat.delete on it, but it seems chat.delete and chat.update can't be called on a message created using chat.postEphemeral.
The Slack message guidelines seems to suggest that a multi-step interactive flow should always be handled in an ephemeral way so that other channel user don't see all intermediate messages before the result but I'm having bad luck figuring out how to get rid of the ephemeral when done. Probably just being bad at reading but any help appreciated.
Edit with more details:
The documentation around using response_url and postEphemeral states
As you replace messages using chat.update or the replace_original
option, you cannot change a message's type from ephemeral to
in_channel. Once a message has been issued, it will retain its
visibility quality for life.
The message guidelines suggest:
If a user has initiated an action that has multiple steps, those steps
should be shown as ephemeral messages visible only to that user until
the entire action is complete to avoid cluttering the channel for
everyone.
Presumably, I should be able to create an interaction in which I first send an in_channel interactive message.
When a user initiates an action, I should be able to send them a series of ephemeral messages using the response_url and passing response_type: 'ephemeral' and replace_original: false?
A new ephemeral interactive message created this way will have its own response_url for making edits, right?
Once I am done with the interactive flow via ephemeral messages, I can modify the original interactive message using its original response_url?
Lastly, how do I get rid of the last ephemeral edit? Or do I just change it to something like "Workflow completed" and hope for the best? I'm asking because Slash commands obviously seem to have a way to essentially replace the ephemeral message for an in_channel message and I'm trying to figure this kind of workflow out.
I searched high and low on how to do this and finally came across the answer.
Your ephemeral message must trigger an action, i.e. button click.
Your response to the action must use the following body
{
'response_type': 'ephemeral',
'text': '',
'replace_original': true,
'delete_original': true
}
'delete_original': true is the key here, which as far as I can tell is not mentioned in any of the API guides, however it is present in the API field guide under Top-level message fields
If you wish to change the response_type of your message instead of deleting it, you must do so by first deleting the ephemeral message and then posting the same message with 'response_type': 'in_channel'.
In my use case I wanted to take an ephemeral message and repost it with the exact same message body as an in-channel message. I have not found a way to retrieve the content of your ephemeral message, so the best method I've found is to pass whatever necessary data spawned your ephemeral message in the button's value so that your action handler can read this data and dynamically recreate the message body.
In my case, this was the user input being used to perform a query. On the off chance that data in the database changes between the time the original ephemeral message is posted and the in-channel version is posted they will be different. You may be able to send a JSON string directly through this value field and avoid making additional database calls and running the risk of messages changing when posted to the channel. The character limit of value is 2000 so JSON passing is extremely limited.
Assuming you use the same code to generate this body when initially creating the ephemeral message and also when recreating it in-channel, you should receive the same body and essentially are able to change an ephemeral message to in-channel message.
Some ephemeral messages can be "soft" deleted/replaced but only when posted as part of a message with interactive features like buttons or menus. When a button is clicked or a menu selection made, you have a chance to instruct Slack to either "delete" the original message, or replace it with a new one. These docs detail using responses and response_url to accomplish that.
A message created with chat.postEphemeral that itself has no interactive features can never be explicitly deleted. Once it's delivered, it's like a ghost and will disappear following a restart or refresh.
Answering your bulleted questions in order:
Correct, you essentially start a new chain of interactivity with net new ephemeral message you post to that user
Each interactive message interaction will have its own response URL. The new ephemeral message won't have a response_url you can use until the end user presses a button, selects a menu item, etc.
response_url will eventually expire ("using the response_url, your app can continue interacting with users up to 5 times within 30 minutes of the action invocation.") If the original message is non-ephemeral, using chat.update is a better strategy for longer timelines. With ephemeral messages, it's more of a "do your best" strategy. They'll eventually get cleaned up for the user after a refresh.
I think you have a good handle on what's best. Personally, I think it's easier to kick off a new "in_channel" message by using chat.postMessage instead of as a chain effect directly from a slash command or interaction.
The Kotlin/Java version for this solution using the Bolt API as shown below
import com.slack.api.bolt.handler.builtin.BlockActionHandler
import com.slack.api.bolt.request.builtin.BlockActionRequest
import com.slack.api.app_backend.interactive_components.response.ActionResponse
import com.slack.api.bolt.response.Response
import com.slack.api.bolt.context.builtin.ActionContext
object Handler : BlockActionHandler {
override fun apply(req: BlockActionRequest,
context: ActionContext): Response {
val response = ActionResponse
.builder()
.deleteOriginal(true)
.replaceOriginal(true)
.responseType("ephemeral")
.blocks(listOf())
.text("")
.build()
context.respond(response)
return context.ack()
}
}
If you are using Python and Flask the following code should work when you respond to a button click in the ephemeral message:
from flask import jsonify
response = jsonify({
'response_type': 'ephemeral',
'text': '',
'replace_original': 'true',
'delete_original':'true'
})
return make_response(response, 200)

Loop through Zapier webhook result

Just trying to figure out if it is possible to loop through Zapier webhook result set and do something with it.
I have a zap that runs once a day and makes a GET request to a specific URL. There's a JSON result that comes back and now I would like to loop through this resultset and fetch emails from each record for using this email list when sending emails with Mailgun.
Any hints?
Yes, that's possible! Zapier can loop through actions up to 25 times. You just need to give it data in the right format (an array of JSON objects).
Here's the steps you'd have in your zap:
Scheduler trigger to run the zap each day
Your webhook GET request
Code step which takes your webhook result, and converts it into an array of JSON objects with email addresses in them
Do something for each email address. You can pick any zapier action, and it will run for every address.
Add any more actions that you want for every email address here...
I think the simpler approach is to use a "Code by Zapier" action and make the HTTP request directly in the code where you can process the response.
Some more info here.

Zapier: How to skip a step in multi-step zaps

I'm looking for a way to skip steps (but not abort the Zap altogether) in a multi-stage Zap. For example, if one of the trigger event's values are a certain value, it doesn't need to run Step 2 (which might have been a creation or deletion step), but should continue on to Step 3.
I believe I can do this by using the Code by Zapier service to call a separate Webhook by Zapier Zap and optionally calling (before that one) another such webhook if it meets a criteria. But that's incredibly hacky.
Zapier now supports branch logic, called Paths. This is currently the best non-code method. It doesn't loop back into a common endpoint, but one could work around that by having them all end with a Webhook POST to a common Webhook receiver that ties up the end portion of the Zap.
Zapier does also support code steps. Obviously, this kind of defeats the purpose of using zapier to avoid coding, but if the step was simple (or maintaining two zaps was hard), you could write out the request and surround it in an if block
if (doThisStep) {
const payload = { name: 'Bob' };
fetch('http://some.example/', { method: 'POST', body: JSON.stringify(payload) })
.then((res) => callback(null, res.json())
.catch(callback);
} else {
// Don't forget the callback if you skip the step!
callback(null, { skipped: true });
}
Zapier now has support for Custom Filters (https://zapier.com/learn/how-to-use-zapier/custom-filters/).
You can setup a filter based on the previous steps that essentially says - "Only proceed to the next step of this Zap if xxx conditions are met".

Resources