Skip to content
This repository has been archived by the owner on Aug 31, 2022. It is now read-only.

Update Ansible configuration for deploying dashboard using runtime environment variables #110

Conversation

gurbirkalsi
Copy link
Collaborator

The initialization of a config.json.j2 file is no longer required as the dashboard bundles the endpoint configuration file (config/endpoints.js) into the final binary deployed to a remote host. In order to reflect this change, the inventory file has been simplified to only reference hosts being deployed to under the [servers] key.

The process for deploying the dashboard to a remote host using Ansible remains the same by running the following:

$ yarn build
$ ansible-playbook -i inventory dashboard-deploy.yml

@gurbirkalsi gurbirkalsi self-assigned this Dec 7, 2020
@gurbirkalsi gurbirkalsi added documentation Improvements or additions to documentation enhancement New feature or request labels Dec 7, 2020
@gurbirkalsi gurbirkalsi added this to the v2.1.0 milestone Dec 7, 2020
Copy link
Member

@portante portante left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But how does the user verify that the yarn build will work with that server?

@gurbirkalsi
Copy link
Collaborator Author

The role for dashboard-install involves installing yarn (in addition to node and npm) on the remote host as part of the installation process.

yarn build needs to be run locally before running the dashboard-deploy role and instructions for using yarn as the default dependency manager is outlined in the root README.md.

@portante
Copy link
Member

portante commented Dec 7, 2020

yarn build needs to be run locally before running the dashboard-deploy role and instructions for using yarn as the default dependency manager is outlined in the root README.md.

But how does one ensure they configuration built into the UMI.js will work with the target server? In other words, the configuration used to build UMI.js file has to work with the target it will be deployed to.

In the past, we did this by having the UMI.js file be independent of the host deployed to, and having a config.json file per-host deployed.

How do we ensure the same with this mechanism?

@gurbirkalsi
Copy link
Collaborator Author

Previously, one would have to confirm config.json.j2 had the correct values by testing API endpoints locally using mock/datastoreConfig.js. Once the user confirmed this, config.json.j2 would be copied to the root directory of the dashboard on the target server. We maintain the same behavior by abstracting this down to a single configuration under config/endpoints.js which can be used for local development and in production.

The main difference between an independent config vs. integrated config is the independent config allows you to edit the configuration after a deploy to a remote host. We are required to build a new dashboard binary for every deployment to a unique target host under the integrated config approach.

The main motivation behind the integrated config approach is to reduce overhead on page components that previously queried the config file on every component mount and allow reference of config endpoints as environment variables at any point after the node process execution. This allows us to initialize bug reporting such as Sentry at the earliest point during the node process execution and optimize the breadth of information collected. Overall, we sacrifice the ability to edit the endpoint config post deployment in order to optimize for faster component performance and earlier tools initialization.

- An inventory file containing the server values defined

## Endpoint Configuration
API endpoints are configured and bundled into the final binary when running `yarn build`. Before deployment of the binary to a remote host, consider the current configuration definition at `config/endpoints.js`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, since I was playing with this yesterday afternoon trying to deploy a dashboard to my VM. I'd forgotten you'd merged the process.env change and initially built and deployed "the old way", which didn't work.

Then when I remembered and edited endpoints.js I found that yarn build didn't include the changes ... I had to delete the dist/ directory contents and rebuild from scratch. Maybe worth a note that apparently just changing endpoints.js and yarn build isn't enough to pick up the changes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running yarn build should overwrite the contents in dist/ according to the UmiJS docs (yarn build maps to umi build in package.json). I was able to deploy the latest dashboard changes to staging without having to delete the dist/ directory. Perhaps clearing the yarn cache might help here? https://classic.yarnpkg.com/en/docs/cli/cache/

@gurbirkalsi
Copy link
Collaborator Author

@portante @dbutenhof I've updated the PR to meet both requirements of referencing the dashboard endpoint config as a standalone component and referencing the dashboard endpoint config through environment variables in the bundled JavaScript code.

This is accomplished by initializing a .env file at the root of the dashboard and using the dotenv library to load environment variables from .env into process.env once the node process boots up (UmiJS does not contain functionality to do this by default). The Ansible scripts have been updated to copy the .env file to the target server as part of the deployment process and can be modified after deployment. I've deployed the dashboard using this model to staging and if you inspect /var/www/html/dashboard/ you should see the .env configuration being referenced.

@dbutenhof
Copy link
Member

@portante @dbutenhof I've updated the PR to meet both requirements of referencing the dashboard endpoint config as a standalone component and referencing the dashboard endpoint config through environment variables in the bundled JavaScript code.

This is accomplished by initializing a .env file at the root of the dashboard and using the dotenv library to load environment variables from .env into process.env once the node process boots up (UmiJS does not contain functionality to do this by default). The Ansible scripts have been updated to copy the .env file to the target server as part of the deployment process and can be modified after deployment. I've deployed the dashboard using this model to staging and if you inspect /var/www/html/dashboard/ you should see the .env configuration being referenced.

I like that better than embedding the parameters in the UMI build; it's much more portable. The idea that Pete and I had kicked around, which is really the basis for my #2024, is to move towards a zero-(pre) configuration, ensuring the dashboard is automatically consistent with the settings of the server it's using by having the server tell it through this new API. The only parameter that would need to be statically configured in your .env would be the port on which the server is running so you could make that query. (E.g., just a GET host:8001/api or some such.) And, ideally, we'll switch over our gunicorn/apache configuration to respond to /api... on the default http/https port so you wouldn't even need that; you'd just do the query back to the host you loaded from.

package.json Outdated
@@ -36,6 +36,7 @@
"ant-design-pro": "^2.1.1",
"antd": "^3.16.1",
"classnames": "^2.2.5",
"dotenv": "^8.2.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the dotenv package do? If I look at the Network tab of a browser console, will I see GET operations fetching the .env file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dotenv reads the contents of the .env file when the Node.js process boots up. It creates an env object as a property of the global process object. This workflow doesn't involve network requests and is the reason we can reference configuration at the earliest point in dashboard initialization.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But is the .env file built into the index.html page that is loaded? Or the <script ...> references in the index.html back on the server? How does Node.js running in the browser get the contents of the .env? Or are you talking about a Node.js process on the server that reads this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bundled dashboard code is configured to read the .env file at the root of the deployed dashboard directory. This is done by adding require('dotenv').config(); to the UmiJS config which is then bundled into the umi.js file referenced in index.html. We don't require a Node.js process to run on a target server in order to serve the .env file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bundled dashboard code is configured to read the .env file at the root of the deployed dashboard directory. This is done by adding require('dotenv').config(); to the UmiJS config which is then bundled into the umi.js file referenced in index.html. We don't require a Node.js process to run on a target server in order to serve the .env file.

So then the umi.js file is built with the contents of the .env file, and then the umi.js file is deployed to the target server?

Do we all agree that the principle of creating a deployable umi.js file once and deploying it to multiple environments is one we want to adhere to as a project?

If we all agree, great.

If we don't agree with that principle, we need to discuss that first because we need a guide for how we handle builds and deployment.

That principle is at the heart of containers (build the container once, deploy it via different orchestrators [podman, kubernetes, others]), of Ansible (roles and playbooks are applied to various inventory files with different contents), and CI/CD pipelines (build once, test the same object without changes through the pipeline until deployed).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bundled dashboard code is configured to read the .env file at the root of the deployed dashboard directory. This is done by adding require('dotenv').config(); to the UmiJS config which is then bundled into the umi.js file referenced in index.html. We don't require a Node.js process to run on a target server in order to serve the .env file.

So then the umi.js file is built with the contents of the .env file, and then the umi.js file is deployed to the target server?

Hmm; good question, but there is a /var/www/html/dashboard/.env on the staging server, and my impression was that dotenv is somehow sucking that in. I don't really see how that could happen with a browser and the static dashboard Javascript on the server unless it's doing an HTML GET to retrieve it just like it does the CSS.

Do we all agree that the principle of creating a deployable umi.js file once and deploying it to multiple environments is one we want to adhere to as a project?

Yes, very strongly!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the "build once, deploy anywhere" paradigm. The aim of this PR is to bridge the gap between deploying to multiple environments with one binary while also allowing endpoint config to be referenced at dashboard initialization. The discussion in this issue captures why this is an uncommon pattern in React environments and generally requires a "hacked" workaround: facebook/create-react-app#2353

I've reviewed suggested workarounds and come up with a solution to configure endpoints at dashboard runtime so we avoid bundling endpoints into the dashboard binary. We do this by referencing <script src="<%= context.config.publicPath +'endpoints.js'%>"></script> in the dashboard index.html template and fetching endpoints.js with a GET request. endpoints.js is included in dist/ as a standalone file and can be modified after deployment.

I've deployed the dashboard to staging using this model. Feel free to inspect the Network tab in dev tools to see the endpoints.js file being queried. Additionally, you can modify endpoints.js and refresh the dashboard to try different API endpoints.

prefix='test_prefix.'
result_index='test_index.'
run_index='test_index.'
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} ?? I don't see an opening brace, so I assume this was an accident?

package.json Outdated
@@ -36,6 +36,7 @@
"ant-design-pro": "^2.1.1",
"antd": "^3.16.1",
"classnames": "^2.2.5",
"dotenv": "^8.2.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bundled dashboard code is configured to read the .env file at the root of the deployed dashboard directory. This is done by adding require('dotenv').config(); to the UmiJS config which is then bundled into the umi.js file referenced in index.html. We don't require a Node.js process to run on a target server in order to serve the .env file.

So then the umi.js file is built with the contents of the .env file, and then the umi.js file is deployed to the target server?

Hmm; good question, but there is a /var/www/html/dashboard/.env on the staging server, and my impression was that dotenv is somehow sucking that in. I don't really see how that could happen with a browser and the static dashboard Javascript on the server unless it's doing an HTML GET to retrieve it just like it does the CSS.

Do we all agree that the principle of creating a deployable umi.js file once and deploying it to multiple environments is one we want to adhere to as a project?

Yes, very strongly!

@gurbirkalsi gurbirkalsi changed the title Update Ansible configuration for deploying dashboard using environment variables Update Ansible configuration for deploying dashboard using runtime environment variables Dec 9, 2020
.env Outdated Show resolved Hide resolved
Comment on lines +9 to +12
<link rel="icon" href="<%= context.config.publicPath +'favicon.ico'%>" type="image/x-icon">

<!-- runtime endpoints config -->
<script src="<%= context.config.publicPath +'endpoints.js'%>"></script>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen that <%= syntax before. What does that actually mean? I tried looking it up, but what I found wasn't really helpful. Is it just a Javascript environment to allow the path concatenation operator?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been adapted from a framework called ejs. It allows for dynamic HTML templating and has some pretty cool features!

@gurbirkalsi gurbirkalsi merged commit a49961c into distributed-system-analysis:master Dec 10, 2020
@gurbirkalsi gurbirkalsi deleted the update_deploy_script branch December 10, 2020 23:25
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation Improvements or additions to documentation enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants