Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular (8) application build once (with production config) and deploy to multiple environments

I have a situation where I’m trying to build my angular application with production config and deploy to multiple environments, say, ng build --configuration=production

The work flow here is when I build using the above command (ng build --configuration=production), the environment.ts file gets replaced with environment.prod.ts

The configurations I have in environment.prod.ts is as follows,

export const environment = {
  production: true,
  environment: 'Production',
  _webApiHost: 'prodsomename.company.com/api/',
};

The configurations I have in environmrnt.test.ts is as follows,

export const environment = {
    production: true,
    environment: 'Test',
    _webApiHost: 'testsomename.company.com/api/',
};

The setting I have on angular.json file is as follows,

"configurations": {
            "production": {
              "fileReplacements": [ {
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.prod.ts"
              } ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [ {
                "type": "initial",
                "maximumWarning": "2mb",
                "maximumError": "5mb"
              } ]
            },
            "test": {
              "fileReplacements": [ {
                "replace": "src/assets/configs/environment.ts",
                "with": "src/assets/configs/environment.test.ts"
              } ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [ {
                "type": "initial",
                "maximumWarning": "2mb",
                "maximumError": "5mb"
              } ]
            }
          }

If I build the solution for every environment separately and deploy to appropriate environment as below figure, enter image description here

it works like charm, which mean the,

testApp communicates to _webApiHost: testsomename.company.com/api/ and prodApp communicates to _webApiHost: prodsomename.company.com/api/

In the above case the artifact which is tested by QA is different from the artifact which is deployed to production, which is not the ideal way of pushing the code to production.

But my concern is I want to build the app only once and deploy it to multiple environments, where each environment will communicate to appropriate api, like below figure, enter image description here

When I build it using the command ng build --configuration=production, the environment.ts file will have production configurations,

export const environment = {
  production: true,
  environment: 'Production',
  _webApiHost: 'prodsomename.company.com/api/',
};

So if that artifact is deployed to test environment, the testApp is trying to communicate with _webApiHost: 'prodsomename.company.com/api/, which is not right.

Here is the Azure DevOps build pipeline powershell script I use to build the solution.

Set-Location "$(Build.Repository.LocalPath)\Buffini.Web.UI\Angular"
Write-Host 'Angular Install Starting'
npm install -g @angular/[email protected] -Verbose
Write-Host 'Angular Install Finished'
Write-Host 'NPM Install Starting'
npm install -Verbose
Write-Host 'NPM Install Finished'
Write-Host 'NPM Update Starting'
npm update -Verbose
Write-Host 'NPM Update Finished'
Write-Host 'NPM Audit Starting'
npm audit fix -Verbose
Write-Host 'NPM Audit Finished'
Write-Host 'Angular Build Starting'
ng build --configuration=production --deleteOutputPath=true
Write-Host 'Angular Build Finished'

I have tried searching for a solution online but I couldn’t find any. Please help me in resolving the issue. I’ll highly appreciate your time and help on this. Thanks in advance.

like image 327
Ananth Asamani Avatar asked Jan 27 '20 19:01

Ananth Asamani


2 Answers

To replace app configurations in runtime time. You need to create config.json file which contains the dynamic configurations (eg. _webApiHost). You can check the example code in this blog to fetch the config.json.

In the you pipeline, you can add extension tasks to replace the config.json contents before deploying to different environment(eg. test, production).

enter image description here

In this way you only need to build your angular app once, and only need replace the config.json contents accordingly before deploying to different environment.

The available extensions you can check out. Magic Chunks task, or RegEx Match & Replace Task. You can check this thread for the example to use these tasks.

like image 65
Levi Lu-MSFT Avatar answered Nov 11 '22 12:11

Levi Lu-MSFT


I won't pretend this an answer, necessarily, but it's long enough of a thought to not fit in the comments area. Perhaps you will find it helpful. (Full disclosure: I'm not using Azure, but rather GitLab. So there would be some translation necessary, regardless, if you find this approach of use.)

Anyway, I was asking the same question a while back. After some digging, I found this link helpful

Using that guidance, I did the following:

First, I do a basic docker build. In that build I have various environment files "ready for the asking" in a folder. The configuration file that actually drives the app is at the root.

I then do another docker build, this one whose sole purpose is to take the first build and give it the desired configuration. (I do it this way because the first build is slow, but I'd like to push to production without rebuilding.)

Next I do the environment build that I want.

For staging, for example: In my GitLab build CI/CD pipeline yaml, I have a line like this....

docker build -t xxxxx --build-arg SERVE_CONFIGURATION=staging -f [A-Docker-File] .

The docker file is the same for all environments, but based upon the passed in argument, this docker build pulls a different file and slams it into the driver's seat. Since there are no secrets in an Angular deployment, it doesn't matter that there are extra (unused) configuration files lurking in the folder structure (though if one were motivated, one could easily delete them.)

Anyway, inside that 2nd docker build, I have...

...
FROM registry.gitlab.com/xxxxxxxxxxxx/compiled as default-config
FROM registry.gitlab.com/xxxxxxxxxxxx/compiled as final-config

COPY --from=default-config /usr/share/nginx/html/environments/environment.$SERVE_CONFIGURATION.js /usr/share/nginx/html/environment.js

So this docker image is nothing more than a build off the prior image, but with the desired environment file in its proper place.

Anyway, I've probably left out some details, but I'm not sure this will help you and will stop here.

like image 40
Wellspring Avatar answered Nov 11 '22 13:11

Wellspring