how to call a Jenkins 2 shared library with parameters and a closure from a Jenkinsfile pipeline? - jenkins-shared-libraries

I would like to call the next shared library from a pipeline:
[a link] https://github.com/docker/jenkins-pipeline-scripts/blob/master/vars/wrappedNode.groovy
I don't know how to call and populate the vars map and the body closure from the Jenkins 2 pipeline.
My shared library name is "vars/my_shared_library.groovy", and their content is
def call(Map vars, Closure body=null) {
vars = vars ?: [:]
def myParameter = vars.get("myParam1",null)
if (body) { body() }
stuff...
}
The Jenkinsfile content is:
#Library 'my_shared_library'
pipeline {
agent none
stages {
stage ('info') {
node {
my_shared_library {
myParam1 = "myValue1"
}
}
}
}
}

To call you custom step from the pipeline, invoke it like this:
node {
my_shared_library(myParam1: "Jose"){
echo "hello"
}
}
To do something in the body with the map handed in, you need to change your step:
def call(Map vars, Closure body=null) {
vars = vars ?: [:]
def myParameter = vars.get("myParam1",null)
if (body) { body(myParameter) }
stuff...
}
And the pipeline to this:
node {
my_shared_library(myParam1: "Jose"){ param ->
echo "hello ${param}"
}
}

Related

listop operator causing infinite recursion, any way to fix?

I'm looking to possibly help update the File::HomeDir module which was never finished. While inspecting it, I noticed that stubbed out methods were causing infinite loops:
In the File::HomeDir role:
unit class File::HomeDir;
use File::HomeDir::Win32;
use File::HomeDir::MacOSX;
use File::HomeDir::Unix;
my File::HomeDir $singleton;
method new
{
return $singleton if $singleton.defined;
if $*DISTRO.is-win {
$singleton = self.bless does File::HomeDir::Win32;
} elsif $*DISTRO.name.starts-with('macos') {
$singleton = self.bless does File::HomeDir::MacOSX;
} else {
$singleton = self.bless does File::HomeDir::Unix;
}
return $singleton;
}
method my-home {
return File::HomeDir.new.my-home;
}
method my-desktop {
return File::HomeDir.new.my-desktop;
}
<snip>
In the File::HomeDir::MacOSX module:
use v6;
unit role File::HomeDir::MacOSX;
method my-home {
# Try HOME on every platform first, because even on Windows, some
# unix-style utilities rely on the ability to overload HOME.
return %*ENV<HOME> if %*ENV<HOME>.defined;
return;
}
method my-desktop {
!!!
}
<snip>
With this code, calling say File::HomeDir.my-desktop; results in an infinite loop.
This module was first written about 5 1/2 years ago. I'm assuming it worked at the time. But it appears now that if a role method has a listop operator, it causes the parent's class to be called which then called the role method which then calls the parent class, etc.
I'd do it like this, staying close to the original design:
role File::HomeDir::Win32 {
method my-home() { dd }
method my-desktop() { dd }
}
role File::HomeDir::MacOSX {
method my-home() { dd }
method my-desktop() { dd }
}
role File::HomeDir::Unix {
method my-home() { dd }
method my-desktop() { dd }
}
class File::HomeDir {
my $singleton;
# Return singleton, make one if there isn't one already
sub singleton() {
without $singleton {
$_ = File::HomeDir but $*DISTRO.is-win
?? File::HomeDir::Win32
!! $*DISTRO.name.starts-with('macos')
?? File::HomeDir::MacOSX
!! File::HomeDir::Unix;
}
$singleton
}
method my-home() { singleton.my-home }
method my-desktop() { singleton.my-desktop }
}
File::HomeDir.my-home;
File::HomeDir.my-desktop;

Java reactor and variable scope

I am trying to get my head around variable propagation using reactor. I have a function as follow where I am trying to pass a variable named requestName from outside the map as follow:
public Mono<ResponseEntity<? extends Object>> myFunction(
final Object request, final String requestName) {
return this.client
.create(request)
.exchangeToMono(
response -> {
final HttpStatus status = response.statusCode();
return response
.bodyToMono(String.class)
.defaultIfEmpty(StringUtils.EMPTY)
.map(
body -> {
if (status.is2xxSuccessful()) {
log.info("{}", requestName);
return ResponseEntity.ok().build();
} else {
return ResponseEntity.badRequest().body(null);
}
});
})
.onErrorResume(ex -> Mono.just(buildErrorFromException(requestName, ex)));
}
or another example would be:
String myvar = "test"
return this.
.login()
.flatMap(
response ->
this.myservice(myvar))
.flatMap(
response2 ->
this.myservice2(myvar))
Is it appropriate ? Or would i need to wrap this function around a Mono.deferContextual and apply a contextView ?
Thanks a lot for your help.

Accessing Context inside an ExchangeFilterFunction

For some reason a context inside the doAfterSuccessOrError method is not available (populated) from the upstream. I've tried to access it using Mono.subscriberContext() (see the snipped). I would expect to have it present but for some reason is not. Am I doing something wrong?
public class LoggingRequestExchangeFunction implements ExchangeFilterFunction {
private final Logger log = LoggerFactory.getLogger(getClass());
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
long start = System.currentTimeMillis();
return next.exchange(request).doAfterSuccessOrError((res, ex) -> {
Mono.subscriberContext().map((ctx -> {
log.info("doAfterSuccessOrError Context {}",ctx);
// log req/res ...
return ctx;
})).subscribe();
}).subscriberContext( ctx -> {
log.info("SubscriberContext: {}" , ctx);
return ctx;
});
}
}
Here is a log output
23:16:59.426 INFO [reactor-http-epoll-2] .p.c.LoggingRequestExchangeFunction [] SubscriberContext: Context1{nexmo-tracing-context=TracingContext{{traceId=f04961da-933a-4d1d-85d5-3bea2c47432f, clientIp=N/A}}}
23:16:59.589 INFO [reactor-http-epoll-2] .p.c.LoggingRequestExchangeFunction [] doAfterSuccessOrError Context Context0{}
The reason is that you create a new Mono inside doAfterSuccessOrError which is independent from the original reactor chain since you subscribe to it separately.
If you just want to log something inside, your alternative is to use doOnEach operator which beside the signal type gives you access to the context as well.
Mono.just("hello")
.doOnEach((signal) ->
{
if (signal.isOnError() || signal.isOnComplete())
{
Context ctx = signal.getContext();
log.info("doAfterSuccessOrError Context {}",ctx);
// log req/res ...
}
})
.subscriberContext( ctx -> {
log.info("SubscriberContext: {}" , ctx);
return ctx;
})
.subscribe();

How to create custom build step based on existing one in TeamCity Kotlin DSL?

I use TeamCity Kotlin DSL 2018.1 to set up build configuration. My settings.kts file looks like this:
version = "2018.1"
project {
buildType {
id("some-id")
name = "name"
steps {
ant {
name = "Step1"
targets = "target1"
mode = antFile { path = "/some/path" }
workingDir = "/some/dir"
jdkHome = "some_jdk"
}
ant {
name = "Step2"
targets = "target2"
mode = antFile { path = "/some/path" }
workingDir = "/some/dir"
jdkHome = "some_jdk"
}
...
}
}
}
It works as expected, but I want to avoid writing the same repeating parameters for every step over and over again.
I tried to write function, which would construct build step pre-filled with default values:
fun customAnt(init: AntBuildStep.() -> kotlin.Unit): AntBuildStep {
val ant_file = AntBuildStep.Mode.AntFile()
ant_file.path = "/some/path"
val ant = AntBuildStep()
ant.mode = ant_file
ant.workingDir = "/some/dir"
ant.jdkHome = "some_jdk"
return ant
}
project {
buildType {
id("some-id")
name = "name"
steps {
customAnt {
name = "Step1"
targets = "target1"
}
customAnt {
name = "Step2"
targets = "target2"
}
...
}
}
}
It compiles but doesn't work: TeamCity just ignores build steps, defined in this way.
Unfortunately, official documentation doesn't contain any information about customizing and extending DSL. Probably, I'm doing something wrong with Kotlin's () -> Unit construction, but can't find out what exactly is wrong.
I got it.
Actually, I was close. The following code works just as I wanted:
version = "2018.1"
fun BuildSteps.customAnt(init: AntBuildStep.() -> Unit): AntBuildStep {
val ant_file = AntBuildStep.Mode.AntFile()
ant_file.path = "/some/path"
val result = AntBuildStep(init)
result.mode = ant_file
result.workingDir = "/some/dir"
result.jdkHome = "some_jdk"
step(result)
return result
}
project {
buildType {
steps {
customAnt {
name = "step1"
targets = "target1"
}
customAnt {
name = "step2"
targets = "target2"
}
...
}
}
}

PHP Memcached extension OOP instantiation

Background:
I have installed the PHP Memcached extension on my live server.
Despite various efforts, I can't seem to install Memcached within my XAMPP development box, so I am relying on the following code to only instantiate Memcached only on the Live server:
My connect file which is included in every page:
// MySQL connection here
// Memcached
if($_SERVER['HTTP_HOST'] != 'test.mytestserver') {
$memcache = new Memcached();
$memcache->addServer('localhost', 11211);
}
At the moment I am instantiating each method, and I can't help thinking that that there is a better way to acheive my objective and wonder if anyone has any ideas?
My class file:
class instrument_info {
// Mysqli connection
function __construct($link) {
$this->link = $link;
}
function execute_query($query, $server) {
$memcache = new Memcached();
$memcache->addServer('localhost', 11211);
$result = mysqli_query($this->link, $query) or die(mysqli_error($link));
$row = mysqli_fetch_array($result);
if($server == 'live')
$memcache->set($key, $row, 86400);
} // Close function
function check_something() {
$memcache = new Memcached();
$memcache->addServer('localhost', 11211);
$query = "SELECT something from somewhere";
if($_SERVER['HTTP_HOST'] != 'test.mytestserver') { // Live server
$key = md5($query);
$get_result = $memcache->get($key);
if($get_result) {
$row = $memcache->get($key);
} else {
$this->execute_query($query, 'live');
}
} else { // Test Server
$this->execute_query($query, 'prod');
}
} // Close function
} // Close Class
I would suggest that you read up on interface-based programming and dependency injection. Here's some example code that might give you an idea about how you should go about it.
interface CacheInterface {
function set($name, $val, $ttl);
function get($name);
}
class MemCacheImpl implements CacheInterface {
/* todo: implement interface */
}
class OtherCacheImpl implements CacheInterface {
/* todo: implement interface */
}
class InstrumentInfo {
private $cache;
private $link;
function __construct($link, $cache) {
$this->link = $link;
$this->cache = $cache;
}
function someFunc() {
$content = $this->cache->get('some-id');
if( !$content ) {
// collect content somehow
$this->cache->set('some-id', $content, 3600);
}
return $content
}
}
define('IS_PRODUCTION_ENV', $_SERVER['HTTP_HOST'] == 'www.my-real-website.com');
if( IS_PRODUCTION_ENV ) {
$cache = new MemCacheImpl();
} else {
$cache = new OtherCacheImpl();
}
$instrumentInfo = new InstrumentInfo($link, $cache);
BTW. You actually have the same problem when it comes to mysqli_query, your'e making your code dependent on a Mysql database and the mysqli extension. All calls to mysqli_query should also be moved out to its own class, representing the database layer.