New to AWS CDK, so please bear with me. I am trying to create multi-deployment CDK, wherein the Dev should be deployed to Account A and prod to Acocunt B.
I have created 2 stacks with the respective account numbers and so on.
mktd_dev_stack = Mktv2Stack(app, "Mktv2Stack-dev",
env=cdk.Environment(account='#####', region='us-east-1'),
stack_name = "myStack-Dev",
# For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html
)
the Prod is similar with the Prod account and different name. When I run them I plan on doing
cdk deploy Mktv2Stack-dev
and simiar for prod.
I am using the cdk 2.xx on Python
What my question is, does this setup give me an ability to pass a parameter, say details which is a dict object of names and criteria for resources that will be set up ? Or is there a way for me to pass parameter/dict from app.py to my program_name.py so that I can look up values from the dict and set them to resources accordingly.
Regards Tanmay
TL;DR Use a single stack and pass in the stg/prod as an env var to app.py.
Pass config down from app.py > Stacks > Constructs as Python Parameters (constructor args). Avoid using CDK Parameters* for config, says AWS's CDK Application Best Practices.
Practically speaking, you pass the account or alias as a environment variable, which app.py reads to perform the metadata lookups and set the stack props. Here's a node-flavoured version of this pattern:
AWS_ACCOUNT=123456789012 npx cdk deploy '*' -a 'node ./bin/app' --profile test-account"
Why not 2 stacks in app.py, one for PROD and one for STAGING?
A 2-stack approach can certainly work. The downsides are that you rarely want to deploy both environments at the same time (outside a CI/CD context). And cross-account permissions are trickier to handle safely if mixed in a single cdk deploy.
Customising Constructs for different environments
Within your code, use a dict, class or whatever to return the configuration you want based on an account or region input. Finally, pass the variables to the constructs. Here's an example of code that uses account, region and isProduction props to customise a s3 bucket:
const queriesBucket = new s3.Bucket(this, 'QueriesBucket', {
bucketName: `${props.appName.toLowerCase()}-queries-${props.env.account}-${
props.env.region
}`,
removalPolicy: props.isProduction
? cdk.RemovalPolicy.RETAIN
: cdk.RemovalPolicy.DESTROY,
versioned: props.isProduction,
lifecycleRules: [
{
id: 'metadata-rule',
prefix: 'metadata',
noncurrentVersionExpiration: props.isProduction
? cdk.Duration.days(30)
: cdk.Duration.days(14),
},
],
});
* "Parameter" has different meaning in Python and CDK. Passing variables between constructs in code using Python Parameters (=method arguments) is a best practice. In CDK-speak a Parameter has the special meaning of a variable value passed to CloudFormation at deploy time. These are not CDK best practice.
Related
I am BRAND new to Quasar Vue3 apps(DevOps helping dev team). We use GitLab for our CICD pipelines and our apps run on OpenShift containers. We also use OpenShift secrets for populating the environment variables for each environment(envFrom) when the container starts. However, we are having a hard time figuring out how to do this with a new Quasar Vue3 app. We've gone through various iterations found on "Google University" and Quasar's documentation, but nothing has helped. We've tried the method of using process.env in the quasar.config.js file:
env: {
myVar: process.env.VUE_APP_VARIABLE
}
However, that seems to be a build-time method and only uses a dummy value we've put into a GitLab CICD variable for testing.
I've also tried the method of using a .js script file defining a function:
export default function getEnv(name) {
return process.env[name];
}
And then importing and calling that function in the MainLayout.vue file:
import getEnv from '../service/env.js'
return {
.
.
myVar: getEnv("VUE_APP_VARIABLE")
}
That works if I return hard-coded string from the script(eg: return "ValueFromScript";), but if I try to return using process.env at all with varied syntaxes, I get blank/null values
return process.env[name];
return process.env."name";
return process.env.VUE_APP_VARIABLE;
return process.env["VUE_APP_VARIABLE"];
etc.
Lastly, we've experemented with the "dotenv" method described here, but that only reads from a .env file.
Can anyone tell me what I'm missing or if this is even possible? I really want to avoid using .env files, it's really not the best practice for production applications.
Thanks.
This is a web application that runs in a browser, you can't access runtime env variables. If you configure FOO: 'test' in quasar.config.js > build > env, then reference it in your app as console.log(process.env.FOO), on build time it will get replaced and turned into console.log('test'). You can check the final code in dist/* to confirm.
You wouldn't need to use a secret management tool here because all the env variables you want to pass to the client application will be seen by users someplace. If you are passing a secret key or similar, then you are probably doing it wrong. You should handle it in the server where it can stay secret instead.
If you are sure the values that will be accessed in the browser are not secret, and all you just want is just them to change on runtime, then you can implement a runtime variable system. It can be done by:
Making an API request on runtime and getting them.
Storing a JSON file somewhere and reading it.
Doing SSR and assigning the variables into ssrContext on the server side. As an example, on the server side, in an SSR middleware, do ssrContext.someVar = process.env.SOME_VAR(env variables are runtime in server-side because they are Node apps that run on a server), then access ssrContext.someVar in the Vue app when the app is rendering on the server side.
If you have some secret things to do, you can do it inside the SSR middleware and return the non-secret result of it to your app using this method as well. So, if this is the case, you can use a secret manager to keep things only available to the Node application which uses the secrets.
Working with our Devs, we came up with a way to setup and use values from OpenShift secrets as environment variables at RUNTIME(should work for K8s in general). I've seen bits and pieces of this in my searches, hopefully I can lay it out in a cohesive format for others that might have the same application requirement.
PreSetup
Create a .sh script file somewhere in your src directory that defines a "getEnv" function as follows. We created a folder for this at src/service:
env.js
export default function getEnv(name) {
return window?.configs?.[name] || process.env[name];
}
Create another .sh script file that writes a json string to another script file to be used later in your code.
This will create another script file dynamically when the container starts up as you will see in later steps.
get-env-vars.sh
JSON_STRING='window.configs = {
"ENV_VAR1": "'"${SECRET_VAR1}"'",
"ENV_VAR2": "'"${SECRET_VAR2}"'"
}'
echo $JSON_STRING >> src/service/config_vars.js
Implementation
In your Dockerfile, add a COPY layer to copy the get-env-vars.sh script to the /docker-entrypoint.d folder.
If you aren't familiar with the docker-entrypoint.d folder; basically, as the container starts up, it will run any .sh file that is located in this folder.
COPY ./get-config-vars.sh /docker-entrypoint.d
Then, in our main index.html file, add the following in the main <head> section to reference/call the script file created by the get-env-vars.sh script at startup:
<script src="/service/config_vars.js"></script>
At this point, we now have a "windows.config" JSON object variable ready for the getEnv() function to pull values from when called.
Wherever you need to be utilizing any of these variables, import the env.js file created earlier to import getEnv() function.
import getEnv from "../service/env.js"
Then simply use the function like you would a variable anywhere else. getEnv("VAR1")
Summary
As an overview here is the workflow the container executes when it is scheduled/started in your K8s environment
Container is scheduled and executes the get-env-vars.sh script, which creates the config_vars.js file
Application starts up. The index.html file executes the config_vars.js file, creating the window.configs JSON object variable
Where needed, the code imports the getEnv() function by importing the env.js file
Calling the getEnv(<variable_name>) function retrieves the value for the specified environment variable from the JSON object variable
When you need to add/update the key-value pairs in your K8s/OpenShift secret, you can delete/restart your POD, which will start the process over, loading in the updated information.
Hopefully this all makes sense.
I have one bitbucket repository, in that, I have 4 CDK stacks in a separate folder. Now I want to create a Jenkins pipeline, Like in the first stage the first stack has been built and deployed,
now I want to use the first stack outputs as second stack's inputs and the same for the third and fouth folders.
To use another stack's output, use the Fn.importValue function.
Like this:
imported_output = cdk.Fn.import_value("OUTPUT_NAME")
A good alternative would be to deploy all of your stacks together in a single CDK app and just pass the object references between your stacks.
CDK will figure out the necessary outputs/imports under the hood, and will deploy the stacks in the correct order automatically.
Here's an example from the docs:
prod = cdk.Environment(account="123456789012", region="us-east-1")
stack1 = StackThatProvidesABucket(app, "Stack1", env=prod)
# stack2 will take a property "bucket"
stack2 = StackThatExpectsABucket(app, "Stack2", bucket=stack1.bucket, env=prod)
I have a CloudFlare Worker where I have environment variables set in the CF Settings..Environment Variables interface. I also have this wrangler.toml
In my worker's index.js I have code reading the variable REGISTRATION_API_URL. If the code is running in a deployed environment then it injects the value from the CF Settings into REGISTRATION_API_URL just fine.
But if I run
wrangler dev
or
wrangler dev --env local
then REGISTRATION_API_URL is undefined.
Originally I expected that the variable would be populated by the CF Settings values, but they aren't. So I tried the two vars setting in the wrangler.toml I show here but no difference. And I have spent a lot of time searching the docs and the greater web.
Are environment variables supported in a local dev environment? Any workarounds that people have come up with? Currently I am looking for undefined and defining the variable with a hard-coded value, but this is not a great answer.
Using wrangler 1.16.0
Thanks.
The docs could be more clear but if you are using the newer module syntax, the variables will not be available as global variables.
Environmental variables with module workers
When deploying a Module Worker, any bindings will not be available as global runtime variables. Instead, they are passed to the handler as a parameter – refer to the FetchEvent documentation for further comparisons and examples .
Here's an example.
export default {
async fetch(request, env, context) {
return new Response(env.MY_VAR);
},
};
KV namespaces are also available in the same object.
Maybe a bit late, but: no I don't think you can
But: you can always use self["YOUR_ENV_VARIABLE"] to get the value and then go from there (unfortunately the docs don't mention that)
Here is what I personally do in my Workers Site project to get the Release version (usually inserted via pipeline/action and then inserted via HtmlRewriter into the index.html):
const releaseVersion = self["RELEASE_VERSION"] || 'unknown'
A custom plugin we wrote for an older version of Jenkins uses an EnvironmentContributingAction to provide environment variables to the execution so they could be used in future build steps and passed as parameters to downstream jobs.
While attempting to convert our build to workflow, I'm having trouble accessing these variables:
node {
// this step queries an API and puts the results in
// environment variables called FE1|BE1_INTERNAL_ADDRESS
step([$class: 'SomeClass', parameter: foo])
// this ends up echoing 'null and null'
echo "${env.FE1_INTERNAL_ADDRESS} and ${env.BE1_INTERNAL_ADDRESS}"
}
Is there a way to access the environment variable that was injected? Do I have to convert this functionality to a build wrapper instead?
EnvironmentContributingAction is currently limited to AbstractBuilds, which WorkflowRuns are not, so pending JENKINS-29537 which I just filed, your plugin would need to be modified somehow. Options include:
Have the builder add a plain Action instead, then register an EnvironmentContributor whose buildEnvironmentFor(Run, …) checks for its presence using Run.getAction(Class).
Switch to a SimpleBuildWrapper which defines the environment variables within a scope, then invoke it from Workflow using the wrap step.
Depend on workflow-step-api and define a custom Workflow Step with comparable functionality but directly returning a List<String> or whatever makes sense in your context. (code sample)
Since PR-2975 is merged, you are able to use new interface:
void buildEnvVars(#Nonnull Run<?, ?> run, #Nonnull EnvVars env, #CheckForNull Node node)
It will be used by old type of builds as well.
What is the recommended way to have different values for application environment variables in an erlang application?
What I mean here is: how do you support different environment in your application (e.g. development, stage, production) in your erlang application? For example I would like tests using a specific fake service on a known host and production code use the real server on a different host.
You can use application config file as well. you can also pass the config as parameter while starting an erlang console that can help you in setting up environment variables. so you are pass test.config or production.config based on environment there by no need to compile the code and start them.
You can find more info here
http://www.erlang.org/doc/man/config.html
Dependency injection.
test_setup() -> [ {host,"http://..."}, ... ].
prod_setup() -> [ {host,"http://..."}, ... ].
test_start() -> start(test_setup()).
prod_start() -> start(prod_setup()).
start(Config) -> ... .
Alternately, policy modules. Make a policy whose interface matches the stuff you need, then pass in the name of the module containing the policy you want. Think ETS/DETS.