I am creating some resources using the for_each method on Terraform version 0.14.15. The resource has an attirbute, input_parameters that takes a string in JSON format as its value. I am defining this value in a map variable utilizing separate objects. The value I am specifying as a string in JSON format, and I am getting an error upon execution that I need to declare a string. Any insight on fixing this error would be helpful. Below is how I have my resource and variable declared.
Resource
resource "aws_config_config_rule" "managed_rules" {
for_each = var.managed_rules
name = each.value.name
description = each.value.description
input_parameters = each.value.input_parameters
source {
owner = each.value.owner
source_identifier = each.value.source_identifier
}
depends_on = [aws_config_configuration_recorder.config_recorder]
}
Variable
variable "managed_rules" {
type = map(object({
name = string
description = string
owner = string
source_identifier = string
# Is there a variable for strings in JSON format?
input_parameters = string
}))
default = {
"1" = {
name = "alb-http-to-https-redirection-check"
description = "Checks whether HTTP to HTTPS redirection is configured on all HTTP listeners of Application Load Balancers. The rule is NON_COMPLIANT if one or more HTTP listeners of Application Load Balancer do not have HTTP to HTTPS redirection configured."
owner = "AWS"
source_identifier = "ALB_HTTP_TO_HTTPS_REDIRECTION_CHECK"
input_parameters = {
"MaximumExecutionFrequency" : "TwentyFour_Hours",
}
}
Error
This default value is not compatible with the variable's type constraint:
element "2": attribute "input_parameters": string required.
After updating code with jsonencode function and changing input_parameters to any, this is the error:
This default value is not compatible with the variable's type constraint:
collection elements cannot be unified.
You have a couple things going on here:
The resource requires input_parameters to be a JSON-encoded string
You have the variable type as a string
You're passing an object type into the variable that only accepts a string type
So (2) and (3) are conflicting. At some point, you have to convert your object into a JSON string. You can either do that before passing it in as an input variable, or change your input variable to accept objects and convert the object to JSON when providing it to the resource.
I'd choose the second option because it's more intuitive to pass the object into the module instead of a string. So, try this:
resource "aws_config_config_rule" "managed_rules" {
for_each = var.managed_rules
name = each.value.name
description = each.value.description
input_parameters = jsonencode(each.value.input_parameters)
source {
owner = each.value.owner
source_identifier = each.value.source_identifier
}
depends_on = [aws_config_configuration_recorder.config_recorder]
}
variable "managed_rules" {
type = map(object({
name = string
description = string
owner = string
source_identifier = string
# Is there a variable for strings in JSON format?
input_parameters = any
}))
default = {
"1" = {
name = "alb-http-to-https-redirection-check"
description = "Checks whether HTTP to HTTPS redirection is configured on all HTTP listeners of Application Load Balancers. The rule is NON_COMPLIANT if one or more HTTP listeners of Application Load Balancer do not have HTTP to HTTPS redirection configured."
owner = "AWS"
source_identifier = "ALB_HTTP_TO_HTTPS_REDIRECTION_CHECK"
input_parameters = {
"MaximumExecutionFrequency" : "TwentyFour_Hours",
}
}
Note that I've used jsonencode in the resource's input_parameters and I've changed the variable type for that field to any (so it will accept an object of any structure).
You can create your json string as follows:
variable "managed_rules" {
type = map(object({
name = string
description = string
owner = string
source_identifier = string
# Is there a variable for strings in JSON format?
input_parameters = string
}))
default = {
"1" = {
name = "alb-http-to-https-redirection-check"
description = "Checks whether HTTP to HTTPS redirection is configured on all HTTP listeners of Application Load Balancers. The rule is NON_COMPLIANT if one or more HTTP listeners of Application Load Balancer do not have HTTP to HTTPS redirection configured."
owner = "AWS"
source_identifier = "ALB_HTTP_TO_HTTPS_REDIRECTION_CHECK"
input_parameters = <<EOL
{
"MaximumExecutionFrequency" : "TwentyFour_Hours",
}
EOL
}
}
}
But then you have to use jsondecode if you want to parse this string. You can't use functions in variables, so it must be done later.
To add to Jordan's answer. I Had a similar concern when trying to add a json policy to a module.
I used the any object type in place of the string object type.
Here's how I fixed it:
Module main.tf
resource "aws_ecr_repository_policy" "main" {
repository = var.repository_name
policy = var.repository_policy
}
Module variables.tf
variable "repository_name" {
type = string
description = "Name of the repository."
}
variable "repository_policy" {
type = any
description = "The policy document. This is a JSON formatted string."
}
Resource Creation main.tf
# ECR Repository for container images
module "ecr_repository_1" {
source = "../../../../modules/aws/ecr-repository"
ecr_repository_name = var.ecr_repository_name.1
image_tag_mutability = var.image_tag_mutability
image_scan_on_push = var.image_scan_on_push
tag_environment = local.tag_environment
tag_terraform = local.tag_terraform.true
}
# ECR Repository policies
module "ecr_repository_policy_1" {
source = "../../../../modules/aws/ecr-repository-policy"
repository_name = var.ecr_repository_name.1
repository_policy = var.repository_policy.1
}
Resource creation variables.tf
variable "ecr_repository_name" {
type = map(string)
description = "Name of the repository."
default = {
"1" = "my-backend-api"
}
}
variable "image_tag_mutability" {
type = string
description = "The tag mutability setting for the repository. Must be one of: MUTABLE or IMMUTABLE. Defaults to MUTABLE."
default = "MUTABLE"
}
variable "image_scan_on_push" {
type = bool
description = "Indicates whether images are scanned after being pushed to the repository (true) or not scanned (false)."
default = true
}
variable "repository_policy" {
type = any
description = "The policy document. This is a JSON formatted string."
default = {
"1" = <<EOF
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "new policy",
"Effect": "Allow",
"Principal": "*",
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:DescribeRepositories",
"ecr:GetRepositoryPolicy",
"ecr:ListImages",
"ecr:DeleteRepository",
"ecr:BatchDeleteImage",
"ecr:SetRepositoryPolicy",
"ecr:DeleteRepositoryPolicy"
]
}
]
}
EOF
}
}
input_parameters = {
"MaximumExecutionFrequency" : "TwentyFour_Hours",
}
This has to be a string instead of Object, Since you defined it as String
I would like to send a POST from MQL4-script, using a JSON-format to a Node-server.
I've tried the webRequest() standard function in MQL4, based on the following documentation, but it did NOT success.
From MQL4 Documentation:
Sending simple requests of type "key=value" using the header `Content-Type: application/x-www-form-urlencoded`.
int WebRequest( const string method, // HTTP method
const string url, // URL
const string cookie, // cookie
const string referer, // referer
int timeout, // timeout
const char &data[], // the array of the HTTP message body
int data_size, // data[] array size in bytes
char &result[], // an array containing server response data
string &result_headers // headers of server response
);
and
Sending a request of any type specifying the custom set of headers for a more flexible interaction with various Web services.
int WebRequest( const string method, // HTTP method
const string url, // URL
const string headers, // headers
int timeout, // timeout
const char &data[], // the array of the HTTP message body
char &result[], // an array containing server response data
string &result_headers // headers of server response
);
Parameters
method [in] HTTP method.
url [in] URL.
headers [in] Request headers of type "key: value", separated by a line break "\r\n".
cookie [in] Cookie value.
referer [in] Value of the Referer header of the HTTP request.
timeout [in] Timeout in milliseconds.
data[] [in] Data array of the HTTP message body.
data_size [in] Size of the data[] array.
result[] [out] An array containing server response data.
result_headers [out] Server response headers.
Returned value:
HTTP server response code or -1 for an error.
Does anyone know how to perfom it?
UPDATE:
Here is the code on the MQL4-script side :
#include <Json\hash.mqh>
#include <Json\KoulJSONMgmt.mqh>
void OnStart()
{
string strParam = StringConcatenate("{","\"currency\"",":","\"",Symbol(),"\"",",","\"timeframe\"",":","\"",IntegerToString(Period()),"\"",",","\"ticktime\"",":","\"",TimeToString(TimeLocal(),TIME_DATE|TIME_SECONDS),"\"",",","\"bid\"",":",DoubleToString(MarketInfo(Symbol(),MODE_BID),4),",","\"ask\"",":",DoubleToString(MarketInfo(Symbol(),MODE_ASK),4),",","\"spread\"",":",DoubleToString(MarketInfo(Symbol(),MODE_SPREAD),0),"}");
JSONParser *parser = new JSONParser();
JSONValue *jv = parser.parse(strParam);
string strJson = jv.toString();
if (jv == NULL) {
Print("error:"+(string)parser.getErrorCode()+parser.getErrorMessage());
} else {
Print("PARSED:"+strJson);
//Example of json String :
//EURUSD,M15: PARSED:{"bid" : 1.1152,"ask" : 1.1154,"spread" : 13,"ticktime" : "2016.10.10 16:24:01","currency" : "EURUSD","timeframe" : "15"}
}
string cookie=NULL,headers;
char post[],result[];
int res;
string strResult,result_header;
headers = "application/json";
prmUrl=StringConcatenate("http://localhost/api"+"/"+"ticks");
//--- Reset the last error code
ResetLastError();
int timeout=1000; //--- Timeout below 1000 (1 sec.) is not enough for slow Internet connection
int intHostNameLength=StringLen(prmParameter);
StringToCharArray(prmParameter,post,0,intHostNameLength);
res=WebRequest("POST",prmUrl,headers,timeout,post,result,result_header);
//--- Checking errors
if(res==-1)
{
Print("Error in WebRequest. Error code =",GetLastError());
//--- Perhaps the URL is not listed, display a message about the necessity to add the address
Print("Add the address '"+prmUrl+"' in the list of allowed URLs on tab 'Expert Advisors'","Error",MB_ICONINFORMATION);
}
else
{
for(int i=0;i<ArraySize(result);i++)
{
if( (result[i] == 10) || (result[i] == 13)) {
continue;
} else {
strResult += CharToStr(result[i]);
}
}
ArrayCopy(strResult,result,0,0,WHOLE_ARRAY);
}
Print(strResult);
}
And the Node side is :
server.js
//Create new Tick
app.post('/api/ticks', function(req, res) {
console.log('Inserting New Tick');
var tick = req.body;
console.log('>'+JSON.stringify(tick,null,4));
Tick.addTick(tick, function(err, tick){
if(err) res.json(err);
res.json(tick);
});
});
and in model ticks.js
var mongoose = require('mongoose');
// User Schema
var TickSchema = mongoose.Schema({
currency:{
type: String
},
timeframe: {
type: String
},
ticktime: {
type: Date
},
bid: {
type: Number
},
ask: {
type: Number
},
spread: {
type: Number
},
createddate :{
type: Date,
default: Date.now
}
}, {collection : 'fxTicks'});
var Tick = module.exports = mongoose.model('Tick', TickSchema);
//Create New Tick
module.exports.addTick = function(tick, callback){
Tick.create(tick, callback);
};
// Get Ticks
module.exports.getTicks = function(callback, limit){
Tick.find(callback).limit(limit);
};
So, back to the square No.1:
In the last-year's post, there was a step by step methodology to proceed with a MCVE-based approach to the problem isolation.
Repeating the same steps here, inside MQL4-code,
adding a python-based mock-up WebSERVER, to diagnose the actual working client/server http-protocol exchange & handshaking, ( not the WebSERVER-side interpretation of the delivered POST-request, which is the same, as if one have launched the URL from a WebBROWSER, for all related details ref: BaseHTTPServer.BaseHTTPRequestHandler )
>>> import BaseHTTPServer
>>> server_class = BaseHTTPServer.HTTPServer
>>> handler_class = BaseHTTPServer.BaseHTTPRequestHandler
>>> httpd = server_class( ( '', 8765 ), handler_class )
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 09:46:45] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 09:46:45] "GET /?test=123_from_Chrome HTTP/1.1" 501 -
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 09:47:23] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 09:47:23] "GET /favicon.ico HTTP/1.1" 501 -
>>>
>>>
>>>
>>> httpd = server_class( ( '', 80 ), handler_class )
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 10:22:05] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 10:22:05] "GET /?test=123_from_Chrome_on_port_80 HTTP/1.1" 501 -
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 10:22:31] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 10:22:31] "GET /?test=123_from_Chrome_on_port_80_again HTTP/1.1" 501 -
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 10:22:34] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 10:22:34] "GET /favicon.ico HTTP/1.1" 501 -
>>> httpd.handle_request()
127.0.0.1 - - [10/Oct/2016 11:25:56] code 501, message Unsupported method ('GET')
127.0.0.1 - - [10/Oct/2016 11:26:12] "GET /?test=123_from_Chrome_on_port_80_another_call HTTP/1.1" 501 -
>>>
>>>
127.0.0.1 - - [10/Oct/2016 12:03:03] code 501, message Unsupported method ('POST')
127.0.0.1 - - [10/Oct/2016 12:03:03] "POST / HTTP/1.1" 501 -
>>>
the output isproviding an evidence that the last pair of rows were produced by an MQL4-side WebRequest() that was setup correctly and works fine there and back[ MetaTrader Terminal 4 ]-Log reads:
2016.10.10 12:03:03.921 ___StackOverflow_WebRequest_DEMO XAUUSD,H1:
DATA:: <head><title>Error response</title></head>
<body>
<h1>Error response</h1><p>Error code 501.<p>
Message: Unsupported method ('POST').<p>
Error code explanation: 501 = Server does not support this operation.
</body>
2016.10.10 12:03:03.921 ___StackOverflow_WebRequest_DEMO XAUUSD,H1:
HDRs:: HTTP/1.0 501 Unsupported method ('POST')
Server: BaseHTTP/0.3 Python/2.7.6
Date: Mon, 10 Oct 2016 20:03:03 GMT
Content-Type: text/html
Connection: close
A raw MQL4-snippet BUT use at one's own risk! ( strongly encourage NOT to use any BLOCKING WebRequest() callsin any Production-grade code...for NON-BLOCKING tools see my other posts and how to or read into internal details on high-performance, low-latency, non-blocking integration tools for distributed heterogeneous systems processing alike ZeroMQ or nanomsg )
All have been warned, so:
Last years setup picture is still valid:
The mock-up WebSERVER had inside the dotted form-field input of:
http://localhost/
One shall also bear in mind, that trying to set a specific port designation in the URL will violate the MetaQuotes Inc. design rule, that a port is being derived from the protocol pragma at the beginning of the URL declaration, so:
http://localhost:8765/
will not work, as MQL4 WebRequest() CANNOT use other port but either of { 80 | 443 }, given by protocol pragma stated in URL: { http: | https: }
Thus for any port-numbering gymnastics, one has to setup and tune a proper set of port-forwarding services, that would leave MetaTrader Terminal 4 live inside this design-cage, using just either of { 80 | 443 }.
The simplest MQL4-script OnStart() demonstrator looks this way:
//+------------------------------------------------------------------+
//| ___StackOverflow_WebRequest_DEMO.mq4 |
//| Copyright © 1987-2016 [MS] |
//| nowhere.no |
//+------------------------------------------------------------------+ >>> https://stackoverflow.com/questions/39954177/how-to-send-a-post-with-a-json-in-a-webrequest-call-using-mql4
#property copyright "Copyright © 1987-2016 [MS]"
#property link "nowhere.no"
#property version "1.00"
#property strict
//+------------------------------------------------------------------+
//| Script program start function |
//+------------------------------------------------------------------+
void OnStart(){
/* A BRIGHTER WAY:
string JSON_string = StringFormat( "{\"currency\": \"%s\", \"timeframe\": \"%d\", \"ticktime\": \"%s\", \"bid\": %f, \"ask\": %f, \"spread\": %s }", _Symbol,
Period(),
TimeToString( TimeLocal(), TIME_DATE | TIME_SECONDS ),
MarketInfo( _Symbol, MODE_BID ),
MarketInfo( _Symbol, MODE_ASK ),
MarketInfo( _Symbol, MODE_SPREAD )
);
// A SMARTER WAY & THE FASTEST PROCESSING TIMES:
// --------------
#define MQL4_COMPILE_TIME_JSON_TEMPLATE "{\"currency\": \"%s\", \"timeframe\": \"%d\", \"ticktime\": \"%s\", \"bid\": %f, \"ask\": %f, \"spread\": %s }" // CONSTANT TEMPLATE TO FILL-IN AD-HOC VALUES:
// +
string JSON_string = StringFormat( MQL4_COMPILE_TIME_JSON_TEMPLATE", _Symbol,
Period(),
TimeToString( TimeLocal(), TIME_DATE | TIME_SECONDS ),
MarketInfo( _Symbol, MODE_BID ),
MarketInfo( _Symbol, MODE_ASK ),
MarketInfo( _Symbol, MODE_SPREAD )
);
*/
string JSON_string = StringConcatenate( "{", // **** MQL4 can concat max 63 items
"\"currency\"",
":",
"\"",
Symbol(),
"\"",
",",
"\"timeframe\"",
":",
"\"",
IntegerToString( Period() ),
"\"",
",",
"\"ticktime\"",
":",
"\"",
TimeToString( TimeLocal(), TIME_DATE | TIME_SECONDS ),
"\"",
",",
"\"bid\"",
":",
DoubleToString( MarketInfo( Symbol(), MODE_BID ), 4 ),
",",
"\"ask\"",
":",
DoubleToString( MarketInfo( Symbol(), MODE_ASK ), 4 ),
",",
"\"spread\"",
":",
DoubleToString( MarketInfo( Symbol(), MODE_SPREAD ), 0 ),
"}"
);
// */
/* off-topic: a JSON-string VALIDATOR -----------------------------------------------------------------------------------------------------------------------------------
#include <Json\hash.mqh>
#include <Json\KoulJSONMgmt.mqh>
JSONParser *parser = new JSONParser();
JSONValue *jv = parser.parse(strParam);
string strJson = jv.toString();
if ( jv == NULL ) Print( "ERROR:" + (string) parser.getErrorCode()
+ parser.getErrorMessage()
);
else Print( "PARSED:" + strJson );
// Example of a journalled Print() for an above setup JSON String :
// EURUSD,M15: PARSED:{"bid" : 1.1152,"ask" : 1.1154,"spread" : 13,"ticktime" : "2016.10.10 16:24:01","currency" : "EURUSD","timeframe" : "15"}
*/ // off-topic: a JSON-string VALIDATOR -----------------------------------------------------------------------------------------------------------------------------------
// string ReqSERVER_URL = "http://localhost:8765/", // **** MQL4 WebRequest CANNOT use other port but either of { 80 | 443 } given by protocol pragma stated in URL: { http: | https: }
string ReqSERVER_URL = "http://localhost/", // ---- MQL4 WebRequst
ReqCOOKIE = NULL,
// ReqHEADERs = "application/json"; // **** MQL4 WebRequest MUST use [in] Request headers of type "key: value", separated by a line break "\r\n".
ReqHEADERs = "Content-Type: application/json\r\n";
int ReqTIMEOUT = 5000; // ---- MQL4 WebRequest SHALL use [in] Timeouts below 1000 (1 sec.) are not enough for slow Internet connection;
// ================================================= // ~~~~ MQL4 WebRequest SHALL be AVOIDED as an un-control-able BLOCKING-SHOW-STOPPER, any professional need shall use NON-BLOCKING tools
char POSTed_DATA[],
result_RECVed_DATA_FromSERVER[];
int result_RetCODE;
string result_DecodedFromSERVER,
result_RECVed_HDRs_FromSERVER;
// int intHostNameLength = StringLen( ReqSERVER_URL );
// StringToCharArray( ReqSERVER_URL, POSTed_DATA, 0, StringLen( ReqSERVER_URL ) );
// StringToCharArray( prmParameter, post, 0, intHostNameLength );
StringToCharArray( JSON_string, POSTed_DATA, 0, StringLen( JSON_string ) );
ResetLastError();
result_RetCODE = WebRequest( "POST",
ReqSERVER_URL,
ReqHEADERs,
ReqTIMEOUT,
POSTed_DATA,
result_RECVed_DATA_FromSERVER,
result_RECVed_HDRs_FromSERVER
);
if ( result_RetCODE == -1 ) Print( "Error in WebRequest. Error code =", GetLastError() ); // returns error 4060 – "Function is not allowed for call" unless permitted -- ref. Picture in >>> https://stackoverflow.com/questions/39954177/how-to-send-a-post-with-a-json-in-a-webrequest-call-using-mql4
else {
for ( int i = 0; i < ArraySize( result_RECVed_DATA_FromSERVER ); i++ ) {
if ( ( result_RECVed_DATA_FromSERVER[i] == 10 ) // == '\n' // <LF>
|| ( result_RECVed_DATA_FromSERVER[i] == 13 ) // == '\r' // <CR>
)
continue;
else result_DecodedFromSERVER += CharToStr( result_RECVed_DATA_FromSERVER[i] );
}
Print( "DATA:: ", result_DecodedFromSERVER );
Print( "HDRs:: ", result_RECVed_HDRs_FromSERVER );
}
}
//+------------------------------------------------------------------+
Deviations from documented steps are easily visible in the source and were left for clarity.
Epilogue:
If documentation says something, it is worth keeping that advice ( with some tests, sure ).
If a sponsored Community advice says something, it is worth giving it at least a try, before asking for more.
It works well for me, I have someone like this :
string response = SendResquest("POST", "GetPrediction", "[4, 7]", "Content-Type: application/json", 5000);
string SendResquest(string httpType, string methodName, string bodyData = "", string headers = "", int timeout)
{
uchar bodyDataCharArray[];
ArrayResize(bodyDataCharArray, StringToCharArray(bodyData, bodyDataCharArray)-1);
int response = WebRequest(httpType, this.address+methodName, headers, timeout, bodyDataCharArray, this.resultDataCharArray, this.resultHeader);
string result = CharArrayToString(this.resultDataCharArray);
if(response == 200)
return result;
Print("Error when trying to call API : ", response);
return "";
}
Allow your [ MetaTrader Terminal 4 ] to communicate with URL via menu:
Tools -> Options -> Expert Advisors ->
1. mark a check-box [ X ] in front of 'Allow WebReq....'
&
2. type the URL name below the check-box, using the green (+) icon, inside the form.
If this doesn't help - try to add Print() statements to see the possible errors ( incorrect MQL4 code or incorrect JSON-format file ).
Have you tried setting the headers like this:
headers = "Content-Type: application/json\r\n";
?