I have been trying to dynamically create the Terraform code for a managed Digital Ocean database I have. I am trying to achieve that I have some lists of FW entries like:
locals {
####################################################################################
## DO object ids (the different ID's for the Postgres databases in Digital Ocean
####################################################################################
id_postgres_application_dev = "12345"
id_postgres_application_stg = "23456"
id_postgres_application_prd = "34567"
# Map to fw for Postgres
pg-application_id = {
"dev" = id_postgres_application_dev
"stg" = id_postgres_application_stg
"prd" = id_postgres_application_prd
}
####################################################################################
## Outside IP addresses
####################################################################################
fw_ip_peter = "4.100.123.140"
fw_ip_sunshine = "152.120.106.102"
####################################################################################
## Postgres Application
####################################################################################
# Map to fw for Postgres
pg-application_fw_rules_ip = {
"dev" = [
local.fw_ip_peter,
local.fw_ip_sunshine]
"stg" = [
local.fw_ip_peter]
"prd" = [
local.fw_ip_peter]
}
long_key = {
type = "string"
default = <<EOF
rule = {
type = "KEY"
value = "VALUE"
}
EOF
}
fw_rules = toset(lookup(local.pg-application_fw_rules_ip, var.environment))
}
Now what I want to achieve is to dynamically generate the FW rule entries (these are described in the Digital Ocean documentation here: https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/database_firewall
So the result would be something like for the dev environment:
id_postgres_application = lookup(local.pg-application_id, var.environment)
resource "digitalocean_database_firewall" "example-fw" {
cluster_id = id_postgres_application
rule {
type = "ip_addr"
value = "4.100.123.140" // Peter
}
rule {
type = "ip_addr"
value = "152.120.106.102" // Sunshine (for dev only)
}
}
So the problem lies in the rule sections - to repeat these per entry in the fw_rules variable.
Does anyone have specific advice on how to do this? I have tried many different solutions, and I think my basic problem is to understand which method to apply?
Generally, you would use dynamic blocks for that. Thus, your code could look like the following:
resource "digitalocean_database_firewall" "example-fw" {
cluster_id = id_postgres_application
dynamic "rule" {
for_each = local.application_fw_rules_ip[var.environment]
content {
type = "ip_addr"
value = rule.key
}
}
}
Treat the code as an example, as probably some further adjustments specific to your setup may be required.
Related
I have an app registration which defines two oauth2_permissions blocks, e.g. (other details elided)
resource "azuread_application" "myapp" {
oauth2_permissions {
is_enabled = true
type = "User"
value = "Permission.One"
}
oauth2_permissions {
is_enabled = true
type = "User"
value = "Permission.Two"
}
}
Which, when applied,works just fine. I then want to refer to those permissions in another app registration, e.g.
resource "azuread_application" "myotherapp" {
required_resource_access {
resource_app_id = azuread_application.myapp.application_id
resource_access {
id = ??
type = "Scope"
}
}
}
For the id here, I have tried:
id = lookup(azuread_application.myapp.oauth2_permissions[0], "id")
which gives This value does not have any indices. As does
id = azuread_application.myapp.oauth2_permissions.0.id
I can define a data block and get the output of oauth2_permissions from myapp:
data "azuread_application" "myapp" {
application_id = azuread_application.myapp.application_id
}
output "myapp-perms" {
value = data.azuread_application.myapp.oauth2_permissions
}
And on apply, that will correctly show an array of the two permission blocks. If I try to refer to the data block instead of the application block, i.e.
id = lookup(data.azuread_application.myapp.oauth2_permissions[0], "id")
This gives me a different error: The given key does not identify an element in this collection value
If I apply those two permissions manually on the console, everything works fine. From reading around I was fairly sure that at least one of the above methods should work but I am clearly missing something.
For completeness, provider definition:
provider "azurerm" {
version = "~> 2.12"
}
provider "azuread" {
version = "~> 0.11.0"
}
Based on comments.
The solution is to use tolist. The reason is that the multiple oauth2_permissions blocks will be represented as sets of objects, which can't be accessed using indices.
id = tolist(azuread_application.myapp.oauth2_permissions)[0].id
However, the sets don't have guaranteed order. Thus a special attention should be payed to this.
Here is the code I am using to create subnets and nsgs I want to associate the NSG and subnet in the same script but I am unable to understand how can I get subnet IDs and NSG IDs which are being produced here and use them in the association resource. Thanks in advance for the help !
First part of code this is being used to create n no of Subnets and NSGs depends upon the parameter
provider "azurerm" {
version = "2.0.0"
features {}
}
resource "azurerm_resource_group" "new-rg" {
name = var.rg_name
location = "West Europe"
}
resource "azurerm_virtual_network" "new-vnet" {
name = var.vnet_name
address_space = ["${var.vnet_address_space}"]
location = azurerm_resource_group.new-rg.location
resource_group_name = azurerm_resource_group.new-rg.name
}
resource "azurerm_subnet" "test" {
count = "${length(var.subnet_prefix)}"
name = "${element(var.subnet_subnetname, count.index)}"
resource_group_name = azurerm_resource_group.new-rg.name
virtual_network_name = azurerm_virtual_network.new-vnet.name
address_prefix = "${element(var.subnet_prefix, count.index)}"
}
resource "azurerm_network_security_group" "new-nsg" {
count = "${length(var.subnet_prefix)}"
name = "${element(var.subnet_subnetname, count.index)}-nsg"
location = azurerm_resource_group.new-rg.location
resource_group_name = azurerm_resource_group.new-rg.name
}
Below is the resource where i have to pass the parameters to create the association for the above subnets and nsgs being created.
Second Part of code Need to make the below code usable for above solution for n no of associations.
resource "azurerm_subnet_network_security_group_association" "example" {
subnet_id = azurerm_subnet.example.id
network_security_group_id = azurerm_network_security_group.example.id
}
How can associate the n number of subnets and nsgs being created by using 2nd part of code, I cant find my way to that
This seems like a good case for for_each. Here is some code I'm using for AWS (the same logic applies as far as I can tell)-
(var.nr_azs is just an int, formatlist is used because for_each only likes strings)
locals {
az_set = toset(formatlist("%s", range(var.nr_azs))) # create a list of numbers and convert them to strings)
}
resource "aws_subnet" "private" {
for_each = local.az_set
availability_zone = random_shuffle.az.result[each.key]
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, each.key)
vpc_id = aws_vpc.main.id
map_public_ip_on_launch = false
}
resource "aws_eip" "nat_gw" {
vpc = true
}
resource "aws_nat_gateway" "gw" {
for_each = aws_subnet.private
allocation_id = aws_eip.nat_gw.id
subnet_id = each.value.id
}
resource "aws_route_table" "private_egress" {
for_each = aws_nat_gateway.gw
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = each.value.id
}
}
resource "aws_route_table_association" "private" {
for_each = local.az_set
subnet_id = aws_subnet.private[each.key].id
route_table_id = aws_route_table.private_egress[each.key].id
}
So i was able to solve the issue mentioned by me above the following code contains the solution for the mentioned scenario for the problem.
resource "azurerm_subnet_network_security_group_association" "snet-nsg-association" {
count = length(var.subnet_subnetname)
subnet_id = element(azurerm_subnet.multi-snet.*.id, count.index)
network_security_group_id = element(azurerm_network_security_group.new-nsg.*.id, count.index)
}
I'm trying to create policies in aws with terraform.
variable "path" {
type = "string"
}
variable "policies" {
type = list(object ({
name = string
plcyfilename = string
asmplcyfilename = string
desc = string
ownner = string}))
default = []
}
resource "aws_iam_policy" "policy" {
count = length(var.policies)
name = lookup(var.policies[count.index], "name")
policy = file(lookup(var.policies[count.index], concat("var.path","plcyfilename")))
description = "Policy for ${lookup(var.policies[count.index], "desc")}"
}
and this is how my tfvars looks like:
path = "./../t2/scripts/"
policies = [
{name = "cwpolicy", plcyfilename = "cw.json" , asmplcyfilename ="csasm.json", desc ="vpcflowlogs", ownner ="vpc"},
]
The error that is thrown while I do this is like this:
Error: Invalid function argument
on main.tf line 13, in resource "aws_iam_policy" "policy":
13: policy = file(lookup(var.policies[count.index], "${concat("${var.path}","plcyfilename")}"))
Invalid value for "seqs" parameter: all arguments must be lists or tuples; got
string.
I'm using terraform 0.12.
It works as expected if I change the variable to have complete file path:plcyfilename=./../t2/scripts/cw.json.
However I want to isolate the file path from the file names.
Can someone point me where I am going wrong.
The concat function is for concatenating lists, not for concatenating strings.
To concatenate strings in Terraform, we use template interpolation syntax:
policy = file("${var.path}/${var.policies[count.index].policy_filename}")
Since your collection of policies is not a sequence where the ordering is significant, I'd recommend also changing this to use resource for_each, which will ensure that Terraform tracks the policies using the policy name strings rather than using the positions in the list:
variable "policies" {
type = map(object({
policy_filename = string
assume_policy_filename = string
description = string
owner = string
}))
default = {}
}
resource "aws_iam_policy" "policy" {
for_each = var.policies
name = each.key
policy = file("${var.path}/${each.value.policy_filename}")
description = "Policy for ${each.value.description}"
}
In this case the policies variable is redefined as being a map, so you'd now present the name of each policy as the key within the map rather than as one of the attributes:
policies = {
cw = {
policy_filename = "cw.json"
assume_policy_filename = "csasm.json"
description = "vpcflowlogs"
owner = "vpc"
}
# ...
}
Because the for_each value is the policies map, each.key inside the resource block is a policy name and each.value is the object representing that policy, making the resulting expressions easier to read and understand.
By using for_each, we will cause Terraform to create resource instance addresses like aws_iam_policy.policy["cw"] rather than like aws_iam_policy.policy[1], and so adding and removing elements from the map will cause Terraform to add and remove corresponding instances from the resource, rather than try to update instances in-place to respect the list ordering as it would've done with your example.
I use classic connecion, which created in bootstap by read parameters from application.ini file. I would like use more external DBs, but I can´t read them from application.ini. I prefer read parameters from main DB (which external DB it is, depend on website). So how effectivelly make connection in model? Now I set that connection every time when I need use it. And when I need use main DB, is neccessary make connection again. It´s very uneffective solution.
function joinClientDB($id)
{
$web = $this->getById($id);
$dbSettings = array();
$dbSettings['host'] = $web['web_dbHost'];
$dbSettings['username'] = $web['web_dbUsername'];
$dbSettings['password'] = $web['web_dbPassword'];
$dbSettings['dbname'] = $web['web_dbName'];
$this->_db = Zend_Db::factory('pdo_mysql', $dbSettings);
$this->_db->query('SET CHARACTER SET ' . $web['web_dbCharset']);
}
function joinDefaultDb()
{
$this->_db = Zend_Registry::get('db');
}
Has anybody easy solution for me?
I would suggest to use the Zend MULTI DB Plugin. Add to your applications.ini all database connections:
; Database ONE
resources.multidb.test.adapter =
resources.multidb.test.dbname =
resources.multidb.test.username =
resources.multidb.test.password = ""
resources.multidb.test.host = "your.local.host"
resources.multidb.test.default = true
; Database TWO
resources.multidb.live.adapter =
resources.multidb.live.dbname =
resources.multidb.live.username =
resources.multidb.live.password = ""
resources.multidb.live.host = "your.live.host.com"
And now you could put them in your registry again (bootstrap.php):
protected function _initDbAdapters()
{
$this->bootstrap('multidb');
$resource = $this->getPluginResource('multidb');
$resource>init();
$testDB = $resource->getDb('test');
$liveDB = $resource->getDb('live');
Zend_Registry::set('DB_TEST', $testDB);
Zend_Registry::set('DB_LIVE', $liveDB);
}
I am new to SalesForce (3 months).
Thus far I have been able to create an application in C# that I can use to preform Inserts and Updates to the SalesForce database. These transactions are one at a time.
No I have the need to preform large scale transactions. For example updating thousands of records at a time. Doing them one by one would quickly put us over our allotted API calls per 24 hour period.
I want to utilize the available bulk transactions process to cut down on the number of API calls. Thus far I have not had much luck coding this nor have I found any such documentation.
If anyone could either provide some generic examples or steer me to reliable documentation on the subject I would greatly appreciate it.
FYI, the data I need to use to do the updates and inserts comes from an IBM Unidata database sitting on an AIX machine. So direct web services communication is not realy possible. Getting the data from Unidata has been my headache. I have that worked out. Now the bulk api to SalesForce is my new headache.
Thanks in advance.
Jeff
You don't mention which API you're currently using, but using the soap partner or enterprise APIs you can write records to salesforce 200 at a time. (the create/update/upsert calls all take an array of SObjects).
Using the bulk API you can send data in chunks of thousands of rows at a time.
You can find the documentation for both sets of APIs here
The answers already given are a good start; however, are you sure you need to actually write a custom app that uses the bulk API? The salesforce data loader is a pretty robust tool, includes a command line interface, and can use either the "normal" or bulk data API's. Unless you are needing to do fancy logic as part of your insert/updates, or some sort of more real-time / on-demand loading, the data loader is going to be a better option than a custom app.
(this is the SOAP code though, not the Salesforce "Bulk API" ; careful not to confuse the two)
Mighy be below code provide clear insight on how to do bulk insertion.
/// Demonstrates how to create one or more Account records via the API
public void CreateAccountSample()
{
Account account1 = new Account();
Account account2 = new Account();
// Set some fields on the account1 object. Name field is not set
// so this record should fail as it is a required field.
account1.BillingCity = "Wichita";
account1.BillingCountry = "US";
account1.BillingState = "KA";
account1.BillingStreet = "4322 Haystack Boulevard";
account1.BillingPostalCode = "87901";
// Set some fields on the account2 object
account2.Name = "Golden Straw";
account2.BillingCity = "Oakland";
account2.BillingCountry = "US";
account2.BillingState = "CA";
account2.BillingStreet = "666 Raiders Boulevard";
account2.BillingPostalCode = "97502";
// Create an array of SObjects to hold the accounts
sObject[] accounts = new sObject[2];
// Add the accounts to the SObject array
accounts[0] = account1;
accounts[1] = account2;
// Invoke the create() call
try
{
SaveResult[] saveResults = binding.create(accounts);
// Handle the results
for (int i = 0; i < saveResults.Length; i++)
{
// Determine whether create() succeeded or had errors
if (saveResults[i].success)
{
// No errors, so retrieve the Id created for this record
Console.WriteLine("An Account was created with Id: {0}",
saveResults[i].id);
}
else
{
Console.WriteLine("Item {0} had an error updating", i);
// Handle the errors
foreach (Error error in saveResults[i].errors)
{
Console.WriteLine("Error code is: {0}",
error.statusCode.ToString());
Console.WriteLine("Error message: {0}", error.message);
}
}
}
}
catch (SoapException e)
{
Console.WriteLine(e.Code);
Console.WriteLine(e.Message);
}
}
Please find the small code which may help you to insert the data into salesforce objects using c# and WSDL APIs. I stuck to much to write code in c#. I assigned using direct index after spiting you can use your ways.
I split the column using | (pipe sign). You may change this and also <br>, \n, etc. (row and column breaking)
Means you can enter N rows which are in your HTML/text file. I wrote the program to add order by my designers who put the order on other website and fetch the data from e-commerce website and who has no interface for the salesforce to add/view the order records. I created one object for the same. and add following columns in the object.
Your suggestions are welcome.
private SforceService binding; // declare the salesforce servive using your access credential
try
{
string stroppid = "111111111111111111";
System.Net.HttpWebRequest fr;
Uri targetUri = new Uri("http://abc.xyz.com/test.html");
fr = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(targetUri);
if ((fr.GetResponse().ContentLength > 0))
{
System.IO.StreamReader str = new System.IO.StreamReader(fr.GetResponse().GetResponseStream());
string allrow = str.ReadToEnd();
string stringSeparators = "<br>";
string[] row1 = Regex.Split(allrow, stringSeparators);
CDI_Order_Data__c[] cord = new CDI_Order_Data__c[row1.Length - 1];
for (int i = 1; i < row1.Length-1; i++)
{
string colstr = row1[i].ToString();
string[] allcols = Regex.Split(colstr, "\\|");
cord[i] = new CDI_Order_Data__c(); // Very important to create object
cord[i].Opportunity_Job_Order__c = stroppid;
cord[i].jobid__c = stroppid;
cord[i].order__c = allcols[0].ToString();
cord[i].firstname__c = allcols[1].ToString();
cord[i].name__c = allcols[2].ToString();
DateTime dtDate = Convert.ToDateTime(allcols[3]);
cord[i].Date__c = new DateTime(Convert.ToInt32(dtDate.Year), Convert.ToInt32(dtDate.Month), Convert.ToInt32(dtDate.Day), 0, 0, 0); //sforcedate(allcols[3]); //XMLstringToDate(allcols[3]);
cord[i].clientpo__c = allcols[4].ToString();
cord[i].billaddr1__c = allcols[5].ToString();
cord[i].billaddr2__c = allcols[6].ToString();
cord[i].billcity__c = allcols[7].ToString();
cord[i].billstate__c = allcols[8].ToString();
cord[i].billzip__c = allcols[9].ToString();
cord[i].phone__c = allcols[10].ToString();
cord[i].fax__c = allcols[11].ToString();
cord[i].email__c = allcols[12].ToString();
cord[i].contact__c = allcols[13].ToString();
cord[i].lastname__c = allcols[15].ToString();
cord[i].Rep__c = allcols[16].ToString();
cord[i].sidemark__c = allcols[17].ToString();
cord[i].account__c = allcols[18].ToString();
cord[i].item__c = allcols[19].ToString();
cord[i].kmatid__c = allcols[20].ToString();
cord[i].qty__c = Convert.ToDouble(allcols[21]);
cord[i].Description__c = allcols[22].ToString();
cord[i].price__c = Convert.ToDouble(allcols[23]);
cord[i].installation__c = allcols[24].ToString();
cord[i].freight__c = allcols[25].ToString();
cord[i].discount__c = Convert.ToDouble(allcols[26]);
cord[i].salestax__c = Convert.ToDouble(allcols[27]);
cord[i].taxcode__c = allcols[28].ToString();
}
try {
SaveResult[] saveResults = binding.create(cord);
}
catch (Exception ce)
{
Response.Write("Buld order update errror" +ce.Message.ToString());
Response.End();
}
if (str != null) str.Close();
}