can I store webjob timer schedule in a child object of appsetting.json? - azure-webjobs

if cron string is stored in the root of appsetting.json file, there is no problem to read them.
However, if I do this in appsetting.json:
"EETHistoryJobSettings": {
"SomeSetting": {
"Name": "A"
},
"Schedule": "*/15 * * * * *"
}
and want the the timer to read from EETHistoryJobSettings json object, it doesn't work (basically can't find EETHistoryJobSetting.Schedule).
public async Task Run([TimerTrigger("%EETHistoryJobSetting.Schedule%", RunOnStartup = true)] TimerInfo timerInfo, ILogger logger,
Just wondering if there is a way to specify Cron time in a child object or has to be on the root?

AFAIK, the Azure Functions Timer Trigger MS Doc is given that CRON Expression Variable should be wrapped in % symbols.
Case 1:
If your appsettings.json contains the code settings in the form of local.settings.json:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "<Storage_Conn_String>",
"CronExpression":"*/15 * * * * *"
}
}
Then the Function Code will be:
public async Task Run([TimerTrigger("%CronExpression%", RunOnStartup = true)] TimerInfo timerInfo, ILogger logger
Case 2:
appsettings.json:
{
"ScheudleInfo":{
"TimerTrigger1":{
"CronExpression":"*/15 * * * * *"
}
}
}
Then the Function Code will be:
public async Task Run([TimerTrigger("%ScheudleInfo:TimerTrigger1:CronExpression%", RunOnStartup = true)] TimerInfo timerInfo, ILogger logger
Note: When using the Azure Function Classes, the Storage account connection string, environment variables should be defined in the local.settings.json.

Related

Laravel Livewire database notification doesn't exist yet when broadcast notification received

I've worked on getting database and broadcast notifications to play nice for quite a while, and I've finally got it working and identified an issue I don't know how to solve.
The root cause seems to be that the notification from pusher is getting back to the app prior to when the database notification is available to select.
This results in my notification icon showing up on the front end, but the notifications array is empty:
public function getListeners()
{
return [
"echo-private:users.{$this->user->id},StatementCompleted" => 'notifyUser',
];
}
public function notifyUser(mixed $notification)
{
if (!empty($notification)) {
$this->showNotificationsBadge = true;
ray('notification!');
$this->refreshNotifications();
ray($this->notifications); <-- THIS IS EMPTY
}
}
public function refreshNotifications()
{
$this->notifications = $this->user->unreadNotifications()->get();
$this->notificationCount = $this->user->unreadNotifications()->count();
}
HOWEVER, if I add sleep(5) into the notifyUser() method like so:
public function notifyUser(mixed $notification)
{
if (!empty($notification)) {
$this->showNotificationsBadge = true;
ray('notification!');
sleep(5); <-- ADDED THIS
$this->refreshNotifications();
}
}
it results in the notifications array containing the notification and everything works fine. So my theory is that it's a timing issue.
How do I ensure my database notification exists before the pusher notification triggers my livewire front-end refresh?
EVENT/LISTENER/NOTIFICATION CLASSES:
Here's my event:
class StatementCompleted implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $statement;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Statement $statement)
{
$this->statement = $statement;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('users.' . $this->statement->uploadedBy->id);
}
}
and here's the event listener class:
class HandleStatementCompletedEvent implements ShouldQueue
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param object $event
* #return void
*/
public function handle($event)
{
Notification::send($event->statement->uploadedBy, new SendStatementCompletedNotification($event->statement));
}
}
and here's the notification class:
class SendStatementCompletedNotification extends Notification implements ShouldQueue, ShouldBroadcast
{
use Queueable;
public $statement;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(Statement $statement)
{
$this->statement = $statement;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['database', 'broadcast'];
}
/**
* Get the broadcastable representation of the notification.
*
* #param mixed $notifiable
* #return BroadcastMessage
*/
public function toBroadcast($notifiable)
{
return new BroadcastMessage([
'title' => 'Statement Processed',
'message' => "Your statement {$this->statement->original_file_name} has been processed.",
'user_id' => $this->statement->uploadedBy->id
]);
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toDatabase($notifiable)
{
return [
'title' => 'Statement Processed',
'message' => "Your statement {$this->statement->original_file_name} has been processed.",
'user_id' => $this->statement->uploadedBy->id
];
}
}

Should a triggered Webjob complete?

I have created a very simple WebJob with a TimerTrigger, for example;
static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddTimers();
});
builder.ConfigureLogging((context, b) =>
{
b.AddConsole();
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
}
public class Functions
{
public static void ProcessTimerMessage([TimerTrigger("*/30 * * * * *", RunOnStartup = true)] TimerInfo timerInfo, ILogger logger)
{
logger.LogInformation("I am here");
}
}
When I run this in Azure it never finishes. I was expecting this to complete on each run and then start again on the next trigger. Instead it never stops running:
The TimerTrigger for WebJobs runs as a singleton instance at startup and will run continuously, internally keeping track of the interval it runs at (based on the provide cron pattern). If any single invocation takes longer than the interval, then it will contiune processing the work it's doing and skip the overlapping invocation until the current work is complete.
You can read the details here:
https://github.com/Azure/azure-webjobs-sdk-extensions/wiki/TimerTrigger

How to manage secret rotation used by spring boot app running on ECS in AWS cloud

My organization is running spring boot app on AWS ECS docker container which reads the credentials for Postgres sql from secrets manager in AWS during boot up. AS part of security complaince, we are rotating the secrets every 3 months. The spring boot app is loosing connection with the database and going down when the RDS credentials are rotated.we have to restart it in order to pick the new credentials to work properly. Is there any way I can read the credentials automatically once the credentials are rotated to avoid restarting the application manually?
After some research I found that the postgres database in AWS supports passwordless authentication using IAM roles. We can generate a token which is valid for 15 mins and can connect to database using that token. I prefer this way of connecting to database rather than using password for my database. More details about setting up password less authentication can be found here
Code example as below
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.rds.auth.GetIamAuthTokenRequest;
import com.amazonaws.services.rds.auth.RdsIamAuthTokenGenerator;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.jdbc.pool.ConnectionPool;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.Properties;
public class RdsIamAuthDataSource extends org.apache.tomcat.jdbc.pool.DataSource {
private static final Logger LOGGER = LoggerFactory.getLogger(RdsIamAuthDataSource.class);
private static final int DEFAULT_PORT = 5432;
private static final String USESSL = "useSSL";
private static final String REQUIRE_SSL = "requireSSL";
private static final String BOOLEAN_TRUE = "true";
private static final String VERIFY_SERVER_CERTIFICATE = "verifyServerCertificate";
private static final String THREAD_NAME = "RdsIamAuthDataSourceTokenThread";
/**
* Constructor for RdsIamAuthDataSource.
* #param props {#link PoolConfiguration}
*/
public RdsIamAuthDataSource(PoolConfiguration props) {
this.poolProperties = props;
}
#Override
public ConnectionPool createPool() throws SQLException {
if (pool == null) {
return createPoolImpl();
} else {
return pool;
}
}
protected ConnectionPool createPoolImpl() throws SQLException {
synchronized (this) {
return pool = new RdsIamAuthConnectionPool(poolProperties);
}
}
private class RdsIamAuthConnectionPool extends ConnectionPool implements Runnable {
private RdsIamAuthTokenGenerator rdsIamAuthTokenGenerator;
private String host;
private String region;
private int port;
private String username;
private Thread tokenThread;
/**
* Constructor for RdsIamAuthConnectionPool.
* #param prop {#link PoolConfiguration}
* #throws SQLException {#link SQLException}
*/
public RdsIamAuthConnectionPool(PoolConfiguration prop) throws SQLException {
super(prop);
}
#Override
protected void init(PoolConfiguration prop) throws SQLException {
try {
final URI uri = new URI(prop.getUrl().substring(5));
this.host = uri.getHost();
this.port = uri.getPort();
if (this.port < 0) {
this.port = DEFAULT_PORT;
}
this.region = StringUtils.split(this.host,'.')[2];
this.username = prop.getUsername();
this.rdsIamAuthTokenGenerator = RdsIamAuthTokenGenerator.builder()
.credentials(new DefaultAWSCredentialsProviderChain())
.region(this.region)
.build();
updatePassword(prop);
final Properties props = prop.getDbProperties();
props.setProperty(USESSL, BOOLEAN_TRUE);
props.setProperty(REQUIRE_SSL, BOOLEAN_TRUE);
props.setProperty(VERIFY_SERVER_CERTIFICATE, BOOLEAN_TRUE);
super.init(prop);
this.tokenThread = new Thread(this, THREAD_NAME);
this.tokenThread.setDaemon(true);
this.tokenThread.start();
} catch (URISyntaxException e) {
LOGGER.error("Database URL is not correct. Please verify", e);
throw new RuntimeException(e.getMessage());
}
}
/**
* Refresh the token every 12 minutes.
*/
#Override
public void run() {
try {
while (this.tokenThread != null) {
Thread.sleep(12 * 60 * 1000);
updatePassword(getPoolProperties());
}
} catch (InterruptedException e) {
LOGGER.error("Background token thread interrupted", e);
}
}
#Override
protected void close(boolean force) {
super.close(force);
final Thread thread = tokenThread;
if (thread != null) {
thread.interrupt();
}
}
private void updatePassword(PoolConfiguration props) {
final String token = rdsIamAuthTokenGenerator.getAuthToken(GetIamAuthTokenRequest.builder()
.hostname(host)
.port(port)
.userName(this.username)
.build());
LOGGER.info("Updated IAM token for connection pool");
props.setPassword(token);
}
}
}
Supply the following DataSource as a spring bean. That's it. Now your application will automatically refresh credentials every 12 minutes
#Bean
public DataSource dataSource() {
final PoolConfiguration props = new PoolProperties();
props.setUrl("jdbc:postgresql://myapp.us-east-2.rds.amazonaws.com/myschema?ssl=true");
props.setUsername("rdsadminuser");
props.setDriverClassName("org.somedatabase.Driver");
return new RdsIamAuthDataSource(props);
}

Azure Webjob 3.0 - Unable to read Configuration variable from appsettings.json in Timer Function

I'm struggling to read any variables from my appsettings.json file in an Azure Webjob SDK 3.0 project.
I am using:
Microsoft.Azure.WebJobs.Core version="3.0.6"
Microsoft.Azure.WebJobs.Extensions version="3.0.2"
Microsoft.Extensions.Configuration version="2.2.0"
I've tried the following:
System.Configuration.ConfigurationManager.AppSettings["ApiUrl"]
but this returns null and fails to read. I've checked the documentation and other questions on SO, but currently it's like finding a needle in a haystack.
Program.cs
static async Task Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices()
.AddAzureStorage()
.AddTimers();
b.UseHostId(Environment.UserName.ToLowerInvariant());
})
.ConfigureAppConfiguration((b, c)=>
{
var env = b.HostingEnvironment;
c.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
c.AddCommandLine(args);
c.AddEnvironmentVariables();
c.Build();
})
.ConfigureLogging((context, b) =>
{
b.SetMinimumLevel(LogLevel.Debug);
b.AddConsole();
string appInsightsKey = context.Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"];
if (!string.IsNullOrEmpty(appInsightsKey))
{
b.AddApplicationInsights(o => o.InstrumentationKey = appInsightsKey);
}
})
.UseConsoleLifetime();
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
Functions.cs
public static async Task ProcessAsync([TimerTrigger("0 */3 * * * *")] TimerInfo timerInfo, ILogger log)
{
//I want to read my appsettings.json here!
}
appsettings.json example:
{
"AzureWebJobsDashboard": "################",
"AzureWebJobsStorage": "################",
"ApiUrl": "#############",
"APPINSIGHTS_INSTRUMENTATIONKEY": "############"
}
IGNORE THIS - STILL not working
Just figured it out! Initialising this at the start of my Functions class fixed it for me:
private readonly IConfiguration _configuration;
public Functions(IConfiguration configuration)
{
_configuration = configuration;
}
public static async Task ProcessAsync([TimerTrigger("0 */3 * * * *")] TimerInfo timerInfo, ILogger log)
{
var conf = _configuration["ApiUrl"];
}
This works, but causes the WebJob to run as one function. Each run should be broken out as each individual run, under the WebJob function parent in the logs. Incredibly frustrating.
If anybody can help, please let me know.

Unit test Laravel middleware

I am trying to write unit tests for my middleware in Laravel. Does anyone know a tutorial, or have an example of this ?
I have been writing a lot of code, but there must be a better way to test the handle method.
Using Laravel 5.2, I am unit testing my middleware by passing it a request with input and a closure with assertions.
So I have a middleware class GetCommandFromSlack that parses the first word of the text field in my Post (the text from a Slack slash command) into a new field called command, then modifies the text field to not have that first word any more. It has one method with the following signature: public function handle(\Illuminate\Http\Request $request, Closure $next).
My Test case then looks like this:
use App\Http\Middleware\GetCommandFromSlack;
use Illuminate\Http\Request;
class CommandsFromSlackTest extends TestCase
{
public function testShouldKnowLiftCommand()
{
$request = new Illuminate\Http\Request();
$request->replace([
'text' => 'lift foo bar baz',
]);
$mw = new \App\Http\Middleware\GetCommandFromSlack;
$mw->handle($request,function($r) use ($after){
$this->assertEquals('lift', $r->input('command'));
$this->assertEquals('foo bar baz',$r->input('text'));
});
}
}
I hope that helps! I'll try to update this if I get more complicated middleware working.
To actually test the middleware class itself you can do:
public function testHandle()
{
$user = new User(['email'=>'...','name'=>'...']);
/**
* setting is_admin to 1 which means the is Admin middleware should
* let him pass, but oc depends on your handle() method
*/
$user->is_admin = 1;
$model = $this->app['config']['auth.model'];
/**
* assuming you use Eloquent for your User model
*/
$userProvider = new \Illuminate\Auth\EloquentUserProvider($this->app['hash'], $model);
$guard = new \Illuminate\Auth\Guard($userProvider, $this->app['session.store']);
$guard->setUser($user);
$request = new \Illuminate\Http\Request();
$middleware = new \YourApp\Http\Middleware\AuthenticateAdmin($guard);
$result = $middleware->handle($request, function(){ return 'can access';});
$this->assertEquals('can access',$result);
}
I thinking the best solution is just checking what happened after middleware. For example, the authentication middleware:
<?php namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Guard;
class Authenticate {
/**
* The Guard implementation.
*
* #var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* #param Guard $auth
* #return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->guest())
{
if ($request->ajax())
{
return response('Unauthorized.', 401);
}
else
{
return redirect()->guest('auth/login');
}
}
return $next($request);
}
}
And my test unit:
<?php
class AuthenticationTest extends TestCase {
public function testIAmLoggedIn()
{
// Login as someone
$user = new User(['name' => 'Admin']);
$this->be($user);
// Call as AJAX request.
$this->client->setServerParameter('HTTP_X-Requested-With', 'XMLHttpRequest');
$this->call('get', '/authpage');
$this->assertEquals(200, $response->getStatusCode());
}
}
I would do it in that way.
I was working on a localization Middleware that sets the app locale based on a URI segment, e.g. http://example.com/ar/foo should set the app local to Arabic. I basically mocked the Request object and tested as normal. Here is my test class:
use Illuminate\Http\Request;
use App\Http\Middleware\Localize;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class LocalizeMiddlewareTest extends TestCase
{
protected $request;
protected $localize;
public function setUp()
{
parent::setUp();
config(['locale' => 'en']);
config(['app.supported_locales' => ['en', 'ar']]);
$this->request = Mockery::mock(Request::class);
$this->localize = new Localize;
}
/** #test */
public function it_sets_the_app_locale_from_the_current_uri()
{
$this->request->shouldReceive('segment')->once()->andReturn('ar');
$this->localize->handle($this->request, function () {});
$this->assertEquals('ar', app()->getLocale());
}
/** #test */
public function it_allows_designating_the_locale_uri_segment()
{
$this->request->shouldReceive('segment')->with(2)->once()->andReturn('ar');
$this->localize->handle($this->request, function () {}, 2);
$this->assertEquals('ar', app()->getLocale());
}
/** #test */
public function it_throws_an_exception_if_locale_is_unsupported()
{
$this->request->shouldReceive('segment')->once()->andReturn('it');
$this->request->shouldReceive('url')->once()->andReturn('http://example.com/it/foo');
$this->setExpectedException(
Exception::class,
"Locale `it` in URL `http://example.com/it/foo` is not supported."
);
$this->localize->handle($this->request, function () {});
}
}
And here is my Middleware class:
namespace App\Http\Middleware;
use Closure;
class Localize
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param integer $localeUriSegment
* #return mixed
*/
public function handle($request, Closure $next, $localeUriSegment = 1)
{
$locale = $request->segment($localeUriSegment);
if (in_array($locale, config('app.supported_locales')))
{
app()->setLocale($locale);
}
else
{
abort(500, "Locale `{$locale}` in URL `".$request->url().'` is not supported.');
}
return $next($request);
}
}
Hope that helps :)