<\!DOCTYPE html> Build & Deployment - Dione Developer Course

🎯 What You'll Learn

  • How Dione's build and deployment pipeline actually works
  • Maven configuration and dependency management
  • Jenkins CI/CD setup and pipeline stages
  • Ansible deployment scripts and automation
  • Environment management and rollback procedures

Build System Overview

⚠️ Deployment Reality

Dione's deployment process is semi-automated. Jenkins handles the build and basic deployment, but manual intervention is often required for production releases. It's not fully automated, but it's reliable.

Deployment Philosophy

πŸ”’

Safety First

Multiple checkpoints and manual approvals for production deployments

πŸ“‹

Checklist Driven

Detailed deployment checklists prevent common mistakes

πŸš€

Environment Progression

Dev β†’ SIT β†’ UAT β†’ Production with testing at each stage

πŸ”„

Rollback Ready

Every deployment includes a tested rollback plan

Deployment Pipeline Architecture

Deployment Flow
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Developer     β”‚    β”‚   Git Repositoryβ”‚    β”‚   Jenkins       β”‚
β”‚   Commits Code  │───▢│   (GitLab)      │───▢│   Build Server  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                         β”‚
                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β–Ό
                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                β”‚   Maven Build   │───▢│   Unit Tests    │───▢│   Package WAR   β”‚
                β”‚   Compile       β”‚    β”‚   Integration   β”‚    β”‚   Create        β”‚
                β”‚   Dependencies  β”‚    β”‚   Tests         β”‚    β”‚   Artifacts     β”‚
                β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                         β”‚
                β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚   Ansible       │───▢│   Environment   │───▢│   Health Check  β”‚
        β”‚   Deployment    β”‚    β”‚   Configuration β”‚    β”‚   Smoke Tests   β”‚
        β”‚   Scripts       β”‚    β”‚   Setup         β”‚    β”‚   Verification  β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Maven Configuration

Dione uses Maven for dependency management and build automation. The configuration is complex due to the modular structure and multiple deployment targets.

Parent POM Structure

pom.xml (Parent)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.currenciesdirect.gtg.ngop</groupId>
    <artifactId>dione-parent</artifactId>
    <version>3.2.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    
    <modules>
        <module>services/business</module>
        <module>services/dioneservices</module>
        <module>customer_portal</module>
        <module>admin_portal</module>
    </modules>
    
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        
        <!-- Dependency Versions -->
        <spring.version>4.3.18.RELEASE</spring.version>
        <hibernate.version>5.2.17.Final</hibernate.version>
        <cxf.version>3.2.7</cxf.version>
        <junit.version>4.12</junit.version>
        
        <!-- Build Properties -->
        <build.timestamp>${maven.build.timestamp}</build.timestamp>
        <maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <!-- Spring Framework -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-framework-bom</artifactId>
                <version>${spring.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            
            <!-- Hibernate -->
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>${hibernate.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
                
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.2.3</version>
                    <configuration>
                        <failOnMissingWebXml>false</failOnMissingWebXml>
                        <archive>
                            <manifestEntries>
                                <Built-By>Jenkins</Built-By>
                                <Build-Time>${build.timestamp}</Build-Time>
                                <Implementation-Version>${project.version}</Implementation-Version>
                            </manifestEntries>
                        </archive>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
    
    <profiles>
        <profile>
            <id>development</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <environment>dev</environment>
            </properties>
        </profile>
        
        <profile>
            <id>sit</id>
            <properties>
                <environment>sit</environment>
            </properties>
        </profile>
        
        <profile>
            <id>uat</id>
            <properties>
                <environment>uat</environment>
            </properties>
        </profile>
        
        <profile>
            <id>production</id>
            <properties>
                <environment>prod</environment>
            </properties>
        </profile>
    </profiles>
</project>

Module-Specific Configuration

services/business/pom.xml
<project>
    <parent>
        <groupId>com.currenciesdirect.gtg.ngop</groupId>
        <artifactId>dione-parent</artifactId>
        <version>3.2.1-SNAPSHOT</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>
    
    <artifactId>business-services</artifactId>
    <packaging>war</packaging>
    <name>Dione Business Services</name>
    
    <dependencies>
        <!-- Spring Dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
        </dependency>
        
        <!-- JPA/Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        
        <!-- Apache CXF for SOAP Services -->
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>${cxf.version}</version>
        </dependency>
        
        <!-- SQL Server Driver -->
        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <version>7.4.1.jre8</version>
        </dependency>
    </dependencies>
    
    <build>
        <finalName>business-services-${environment}</finalName>
        
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <webResources>
                        <resource>
                            <directory>src/main/resources/config/${environment}</directory>
                            <targetPath>WEB-INF/classes</targetPath>
                            <filtering>true</filtering>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>
            
            <!-- Generate WSDL documentation -->
            <plugin>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-java2ws-plugin</artifactId>
                <version>${cxf.version}</version>
                <executions>
                    <execution>
                        <id>generate-wsdl</id>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>java2ws</goal>
                        </goals>
                        <configuration>
                            <className>com.currenciesdirect.gtg.ngop.business.services.auth.WSAuth</className>
                            <outputFile>${project.build.directory}/wsdl/AuthService.wsdl</outputFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Build Commands

Common Maven Commands
# Clean and compile all modules
mvn clean compile

# Run tests and package for SIT environment
mvn clean package -Psit

# Skip tests and package for production
mvn clean package -Pproduction -DskipTests

# Deploy to local repository
mvn clean install

# Generate dependency tree for debugging
mvn dependency:tree

# Check for newer versions of dependencies
mvn versions:display-dependency-updates

# Create deployment artifacts
mvn clean package -Pproduction -DskipTests assembly:single

Jenkins CI/CD Pipeline

Jenkins orchestrates our build and deployment process with separate pipelines for different environments.

Pipeline Configuration

Jenkinsfile (Production Pipeline)
pipeline {
    agent {
        label 'maven-build-slave'
    }
    
    environment {
        MAVEN_HOME = '/opt/maven'
        JAVA_HOME = '/opt/java/openjdk-8'
        PATH = "${MAVEN_HOME}/bin:${JAVA_HOME}/bin:${PATH}"
        
        // Deployment settings
        DEPLOY_ENV = 'production'
        WAR_FILE_NAME = "business-services-${DEPLOY_ENV}.war"
        BACKUP_DIR = "/opt/deployments/backups"
        DEPLOY_DIR = "/opt/jboss/standalone/deployments"
    }
    
    options {
        buildDiscarder(logRotator(numToKeepStr: '10'))
        timeout(time: 30, unit: 'MINUTES')
        timestamps()
    }
    
    stages {
        stage('Checkout') {
            steps {
                script {
                    // Checkout specific branch or tag
                    checkout scm
                    
                    // Get commit info for tracking
                    env.GIT_COMMIT_SHORT = sh(
                        script: 'git rev-parse --short HEAD',
                        returnStdout: true
                    ).trim()
                    
                    env.BUILD_VERSION = "${env.BUILD_NUMBER}-${env.GIT_COMMIT_SHORT}"
                }
                
                echo "Building version: ${env.BUILD_VERSION}"
            }
        }
        
        stage('Build') {
            steps {
                script {
                    try {
                        // Clean and compile
                        sh 'mvn clean compile -P${DEPLOY_ENV}'
                        
                        // Check for common issues
                        sh '''
                            # Check for missing properties files
                            find . -name "*.properties.template" | while read template; do
                                actual="${template%.template}"
                                if [ ! -f "$actual" ]; then
                                    echo "ERROR: Missing properties file: $actual"
                                    exit 1
                                fi
                            done
                        '''
                        
                    } catch (Exception e) {
                        error "Build failed: ${e.getMessage()}"
                    }
                }
            }
        }
        
        stage('Test') {
            steps {
                script {
                    try {
                        // Run unit tests
                        sh 'mvn test -P${DEPLOY_ENV}'
                        
                        // Run integration tests (if not production)
                        if (env.DEPLOY_ENV != 'production') {
                            sh 'mvn failsafe:integration-test -P${DEPLOY_ENV}'
                        }
                        
                    } catch (Exception e) {
                        // Don't fail production builds on test failure, but warn
                        if (env.DEPLOY_ENV == 'production') {
                            echo "WARNING: Tests failed, but continuing with production build"
                            currentBuild.result = 'UNSTABLE'
                        } else {
                            error "Tests failed: ${e.getMessage()}"
                        }
                    }
                }
            }
            
            post {
                always {
                    // Archive test results
                    publishTestResults testResultsPattern: '**/target/surefire-reports/*.xml'
                    publishTestResults testResultsPattern: '**/target/failsafe-reports/*.xml'
                }
            }
        }
        
        stage('Package') {
            steps {
                script {
                    try {
                        // Package WAR files
                        sh 'mvn package -P${DEPLOY_ENV} -DskipTests'
                        
                        // Verify WAR files were created
                        sh '''
                            for war in $(find . -name "*.war" -type f); do
                                echo "Created WAR: $war"
                                echo "Size: $(du -h $war | cut -f1)"
                                
                                # Basic WAR validation
                                if ! unzip -t "$war" > /dev/null 2>&1; then
                                    echo "ERROR: Invalid WAR file: $war"
                                    exit 1
                                fi
                            done
                        '''
                        
                        // Archive artifacts
                        archiveArtifacts artifacts: '**/target/*.war', fingerprint: true
                        
                    } catch (Exception e) {
                        error "Packaging failed: ${e.getMessage()}"
                    }
                }
            }
        }
        
        stage('Security Scan') {
            when {
                environment name: 'DEPLOY_ENV', value: 'production'
            }
            steps {
                script {
                    try {
                        // OWASP dependency check
                        sh 'mvn org.owasp:dependency-check-maven:check'
                        
                        // Archive security report
                        publishHTML([
                            allowMissing: false,
                            alwaysLinkToLastBuild: true,
                            keepAll: true,
                            reportDir: 'target',
                            reportFiles: 'dependency-check-report.html',
                            reportName: 'Security Scan Report'
                        ])
                        
                    } catch (Exception e) {
                        echo "Security scan failed: ${e.getMessage()}"
                        currentBuild.result = 'UNSTABLE'
                    }
                }
            }
        }
        
        stage('Deploy') {
            when {
                anyOf {
                    branch 'master'
                    branch 'release/*'
                }
            }
            steps {
                script {
                    // Require manual approval for production
                    if (env.DEPLOY_ENV == 'production') {
                        input message: 'Deploy to Production?', 
                              ok: 'Deploy',
                              submitterParameter: 'DEPLOYER'
                        
                        echo "Production deployment approved by: ${env.DEPLOYER}"
                    }
                    
                    // Call Ansible deployment
                    build job: 'ansible-deploy-dione',
                          parameters: [
                              string(name: 'ENVIRONMENT', value: env.DEPLOY_ENV),
                              string(name: 'BUILD_NUMBER', value: env.BUILD_NUMBER),
                              string(name: 'WAR_ARTIFACT_URL', value: "${env.BUILD_URL}artifact/services/business/target/${WAR_FILE_NAME}")
                          ]
                }
            }
        }
    }
    
    post {
        always {
            // Clean workspace
            cleanWs()
        }
        
        success {
            // Notify teams on successful production deployment
            script {
                if (env.DEPLOY_ENV == 'production') {
                    slackSend channel: '#deployment-notifications',
                             color: 'good',
                             message: "βœ… Dione ${env.BUILD_VERSION} successfully deployed to production"
                }
            }
        }
        
        failure {
            // Notify teams on failure
            slackSend channel: '#deployment-notifications',
                     color: 'danger',
                     message: "❌ Dione build ${env.BUILD_NUMBER} failed in ${env.STAGE_NAME} stage"
        }
        
        unstable {
            // Notify teams on unstable build
            slackSend channel: '#deployment-notifications',
                     color: 'warning',
                     message: "⚠️ Dione build ${env.BUILD_NUMBER} completed with warnings"
        }
    }
}

Jenkins Job Configuration

Jenkins Job Settings
JOB CONFIGURATION:

Build Triggers:
- Poll SCM: H/5 * * * * (every 5 minutes)
- GitHub webhook for push events
- Manual trigger with parameters

Build Environment:
- Delete workspace before build starts
- Set build timeout: 30 minutes
- Use custom workspace: /var/jenkins_home/workspace/dione-${ENVIRONMENT}

Build Parameters:
- Environment: Choice (dev, sit, uat, production)
- Skip Tests: Boolean (default: false)
- Deploy After Build: Boolean (default: true)
- Git Branch: String (default: master)

Post-build Actions:
- Archive artifacts: **/*.war
- Publish JUnit results: **/target/surefire-reports/*.xml
- Slack notifications
- Email notifications to team leads

Ansible Deployment

Ansible handles the actual deployment to servers, configuration management, and service orchestration.

Deployment Playbook

deploy-dione.yml
---
- name: Deploy Dione Application
  hosts: "{{ target_environment }}"
  become: yes
  serial: 1  # Deploy one server at a time
  
  vars:
    app_name: dione
    app_user: jboss
    jboss_home: /opt/jboss
    deploy_dir: "{{ jboss_home }}/standalone/deployments"
    backup_dir: "/opt/deployments/backups"
    war_file: "business-services-{{ environment }}.war"
    health_check_url: "http://localhost:8080/business-services/health"
    
  pre_tasks:
    - name: Check if this is a production deployment
      set_fact:
        is_production: "{{ environment == 'production' }}"
    
    - name: Require confirmation for production deployment
      pause:
        prompt: "This will deploy to PRODUCTION. Type 'yes' to continue"
      when: is_production
      register: production_confirm
    
    - name: Fail if production deployment not confirmed
      fail:
        msg: "Production deployment cancelled"
      when: is_production and production_confirm.user_input != "yes"
  
  tasks:
    - name: Create backup directory
      file:
        path: "{{ backup_dir }}/{{ ansible_date_time.epoch }}"
        state: directory
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
        mode: '0755'
      
    - name: Stop JBoss service
      systemd:
        name: jboss
        state: stopped
      register: jboss_stop
      
    - name: Wait for JBoss to fully stop
      wait_for:
        port: 8080
        host: localhost
        state: stopped
        timeout: 60
      
    - name: Backup current deployment
      copy:
        src: "{{ deploy_dir }}/{{ war_file }}"
        dest: "{{ backup_dir }}/{{ ansible_date_time.epoch }}/{{ war_file }}"
        remote_src: yes
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
      ignore_errors: yes  # File might not exist on first deployment
      
    - name: Remove old deployment
      file:
        path: "{{ deploy_dir }}/{{ war_file }}"
        state: absent
        
    - name: Remove deployment markers
      file:
        path: "{{ deploy_dir }}/{{ war_file }}.deployed"
        state: absent
        
    - name: Download new WAR file from Jenkins
      get_url:
        url: "{{ war_artifact_url }}"
        dest: "{{ deploy_dir }}/{{ war_file }}"
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
        mode: '0644'
        timeout: 300
      register: war_download
      
    - name: Verify WAR file integrity
      command: unzip -t "{{ deploy_dir }}/{{ war_file }}"
      register: war_verify
      failed_when: war_verify.rc != 0
      
    - name: Update configuration files
      template:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
        mode: '0644'
        backup: yes
      loop:
        - { src: "ngop.properties.j2", dest: "{{ jboss_home }}/standalone/configuration/ngop.properties" }
        - { src: "standalone.xml.j2", dest: "{{ jboss_home }}/standalone/configuration/standalone.xml" }
      notify:
        - restart jboss
        
    - name: Start JBoss service
      systemd:
        name: jboss
        state: started
        enabled: yes
      register: jboss_start
      
    - name: Wait for JBoss to start
      wait_for:
        port: 8080
        host: localhost
        state: started
        timeout: 300
        
    - name: Wait for application deployment
      uri:
        url: "{{ health_check_url }}"
        method: GET
        status_code: 200
      register: health_check
      until: health_check.status == 200
      retries: 30
      delay: 10
      
    - name: Verify deployment success
      uri:
        url: "{{ health_check_url }}"
        method: GET
        return_content: yes
      register: health_response
      
    - name: Display deployment status
      debug:
        msg: |
          Deployment Status: SUCCESS
          Application Version: {{ health_response.json.version | default('Unknown') }}
          Deployment Time: {{ ansible_date_time.iso8601 }}
          Server: {{ inventory_hostname }}
          
  handlers:
    - name: restart jboss
      systemd:
        name: jboss
        state: restarted
        
  post_tasks:
    - name: Run smoke tests
      include_tasks: smoke-tests.yml
      
    - name: Clean old backups (keep last 5)
      shell: |
        cd {{ backup_dir }}
        ls -1t | tail -n +6 | xargs -r rm -rf
      
    - name: Send deployment notification
      uri:
        url: "{{ slack_webhook_url }}"
        method: POST
        body_format: json
        body:
          text: "βœ… Dione deployed successfully to {{ environment }} on {{ inventory_hostname }}"
      when: slack_webhook_url is defined

Configuration Templates

templates/ngop.properties.j2
# Dione Configuration for {{ environment }} environment
# Generated on {{ ansible_date_time.iso8601 }}

# Database Configuration
jdbc.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.url=jdbc:sqlserver://{{ db_host }}:{{ db_port }};databaseName={{ db_name }};integratedSecurity=false
jdbc.username={{ db_username }}
jdbc.password={{ db_password }}

# Connection Pool Settings
jdbc.initialPoolSize={{ db_pool_initial_size | default(5) }}
jdbc.minPoolSize={{ db_pool_min_size | default(5) }}
jdbc.maxPoolSize={{ db_pool_max_size | default(50) }}
jdbc.maxIdleTime={{ db_pool_max_idle_time | default(300) }}

# External Service URLs
{% if environment == 'production' %}
ngop.sf.connectionurl={{ salesforce_prod_url }}
pe.serivce.host={{ pricing_engine_prod_host }}
commhub.service.url={{ commhub_prod_url }}
{% else %}
ngop.sf.connectionurl={{ salesforce_test_url }}
pe.serivce.host={{ pricing_engine_test_host }}
commhub.service.url={{ commhub_test_url }}
{% endif %}

# Email Configuration
email.smtp.host={{ smtp_host }}
email.smtp.port={{ smtp_port }}
email.smtp.username={{ smtp_username }}
email.smtp.password={{ smtp_password }}
email.smtp.auth={{ smtp_auth | default('true') }}
email.smtp.starttls.enable={{ smtp_starttls | default('true') }}

# Logging Configuration
log.level.root={{ log_level_root | default('INFO') }}
log.level.application={{ log_level_app | default('DEBUG') }}
log.file.path={{ log_file_path | default('/opt/jboss/logs/application.log') }}

# Cache Configuration
cache.provider=infinispan
cache.config.file={{ jboss_home }}/standalone/configuration/infinispan-config.xml

# Environment Specific Settings
environment.name={{ environment }}
deployment.timestamp={{ ansible_date_time.epoch }}
deployment.version={{ app_version | default('unknown') }}

Inventory Configuration

inventory/production.yml
all:
  children:
    production:
      hosts:
        dione-prod-web01:
          ansible_host: 10.0.1.10
          server_role: web
        dione-prod-web02:
          ansible_host: 10.0.1.11
          server_role: web
        dione-prod-app01:
          ansible_host: 10.0.1.20
          server_role: app
        dione-prod-app02:
          ansible_host: 10.0.1.21
          server_role: app
      vars:
        environment: production
        
        # Database Configuration
        db_host: prod-sql-cluster.internal
        db_port: 1433
        db_name: NGOP_PROD_DB
        db_username: "{{ vault_db_username }}"
        db_password: "{{ vault_db_password }}"
        
        # Connection Pool Sizing
        db_pool_initial_size: 10
        db_pool_min_size: 10
        db_pool_max_size: 100
        db_pool_max_idle_time: 300
        
        # External Services
        salesforce_prod_url: "https://login.salesforce.com/services/Soap/c/38.0"
        pricing_engine_prod_host: "pricing-prod.internal"
        commhub_prod_url: "https://commhub-prod.internal/commhub/"
        
        # Email Configuration
        smtp_host: "smtp.office365.com"
        smtp_port: 587
        smtp_username: "{{ vault_smtp_username }}"
        smtp_password: "{{ vault_smtp_password }}"
        
        # Logging
        log_level_root: "WARN"
        log_level_app: "INFO"
        log_file_path: "/opt/jboss/logs/dione-production.log"
        
        # Notification
        slack_webhook_url: "{{ vault_slack_webhook_url }}"

Environment Management

Managing multiple environments with different configurations and deployment schedules.

Environment Hierarchy

Environment Configuration
ENVIRONMENT PROGRESSION:

Development (DEV)
β”œβ”€β”€ Purpose: Developer testing and integration
β”œβ”€β”€ Database: Shared dev database with test data
β”œβ”€β”€ External Services: Mock services or dev endpoints
β”œβ”€β”€ Deployment: Automatic on commit to develop branch
β”œβ”€β”€ Monitoring: Basic application logs
└── Access: All developers

System Integration Testing (SIT)
β”œβ”€β”€ Purpose: Integration testing and QA validation
β”œβ”€β”€ Database: Dedicated SIT database with production-like data
β”œβ”€β”€ External Services: Test endpoints, limited functionality
β”œβ”€β”€ Deployment: Manual trigger after dev validation
β”œβ”€β”€ Monitoring: Application + performance monitoring
└── Access: QA team, senior developers

User Acceptance Testing (UAT)
β”œβ”€β”€ Purpose: Business user testing and sign-off
β”œβ”€β”€ Database: Production copy (sanitized)
β”œβ”€β”€ External Services: Production-like test endpoints
β”œβ”€β”€ Deployment: Manual trigger after SIT approval
β”œβ”€β”€ Monitoring: Full monitoring stack
└── Access: Business users, product owners

Production (PROD)
β”œβ”€β”€ Purpose: Live customer-facing environment
β”œβ”€β”€ Database: Production database cluster
β”œβ”€β”€ External Services: Live production endpoints
β”œβ”€β”€ Deployment: Manual trigger with approvals
β”œβ”€β”€ Monitoring: Full monitoring + alerting
└── Access: Limited to production support team

Configuration Management

Environment-Specific Settings
# Environment configuration script
#!/bin/bash

ENVIRONMENT=$1
CONFIG_DIR="/opt/dione/config"

case $ENVIRONMENT in
    "dev")
        echo "Configuring Development environment..."
        
        # Database settings
        DB_HOST="dev-sql.internal"
        DB_NAME="NGOP_DEV_DB"
        DB_POOL_SIZE="5"
        
        # External services
        USE_MOCK_SERVICES="true"
        PRICING_ENGINE_HOST="mock-pricing.dev"
        SALESFORCE_URL="https://test.salesforce.com"
        
        # Logging
        LOG_LEVEL="DEBUG"
        LOG_TO_CONSOLE="true"
        
        ;;
    "sit")
        echo "Configuring SIT environment..."
        
        # Database settings
        DB_HOST="sit-sql.internal"
        DB_NAME="NGOP_SIT_DB"
        DB_POOL_SIZE="10"
        
        # External services
        USE_MOCK_SERVICES="false"
        PRICING_ENGINE_HOST="pricing-sit.internal"
        SALESFORCE_URL="https://test.salesforce.com"
        
        # Logging
        LOG_LEVEL="INFO"
        LOG_TO_CONSOLE="false"
        
        ;;
    "uat")
        echo "Configuring UAT environment..."
        
        # Database settings
        DB_HOST="uat-sql.internal"
        DB_NAME="NGOP_UAT_DB"
        DB_POOL_SIZE="15"
        
        # External services
        USE_MOCK_SERVICES="false"
        PRICING_ENGINE_HOST="pricing-uat.internal"
        SALESFORCE_URL="https://test.salesforce.com"
        
        # Logging
        LOG_LEVEL="INFO"
        LOG_TO_CONSOLE="false"
        
        ;;
    "production")
        echo "Configuring Production environment..."
        
        # Database settings
        DB_HOST="prod-sql-cluster.internal"
        DB_NAME="NGOP_PROD_DB"
        DB_POOL_SIZE="50"
        
        # External services
        USE_MOCK_SERVICES="false"
        PRICING_ENGINE_HOST="pricing-prod.internal"
        SALESFORCE_URL="https://login.salesforce.com"
        
        # Logging
        LOG_LEVEL="WARN"
        LOG_TO_CONSOLE="false"
        
        ;;
    *)
        echo "Unknown environment: $ENVIRONMENT"
        exit 1
        ;;
esac

# Generate configuration file
cat > $CONFIG_DIR/ngop.properties << EOF
# Generated configuration for $ENVIRONMENT environment
# Timestamp: $(date)

jdbc.url=jdbc:sqlserver://$DB_HOST:1433;databaseName=$DB_NAME
jdbc.maxPoolSize=$DB_POOL_SIZE

use.mock.services=$USE_MOCK_SERVICES
pe.service.host=$PRICING_ENGINE_HOST
sf.connection.url=$SALESFORCE_URL

log.level.root=$LOG_LEVEL
log.console.enabled=$LOG_TO_CONSOLE

environment.name=$ENVIRONMENT
EOF

echo "Configuration generated: $CONFIG_DIR/ngop.properties"

Rollback Procedures

When deployments go wrong, having a tested rollback plan is critical for financial services.

Rollback Playbook

rollback-dione.yml
---
- name: Rollback Dione Application
  hosts: "{{ target_environment }}"
  become: yes
  serial: 1
  
  vars:
    app_name: dione
    app_user: jboss
    jboss_home: /opt/jboss
    deploy_dir: "{{ jboss_home }}/standalone/deployments"
    backup_dir: "/opt/deployments/backups"
    war_file: "business-services-{{ environment }}.war"
    
  pre_tasks:
    - name: Check if this is a production rollback
      set_fact:
        is_production: "{{ environment == 'production' }}"
    
    - name: Require confirmation for production rollback
      pause:
        prompt: "This will ROLLBACK PRODUCTION. Type 'rollback' to continue"
      when: is_production
      register: production_confirm
    
    - name: Fail if production rollback not confirmed
      fail:
        msg: "Production rollback cancelled"
      when: is_production and production_confirm.user_input != "rollback"
    
    - name: Find latest backup
      find:
        paths: "{{ backup_dir }}"
        file_type: directory
        use_regex: yes
        patterns: '^[0-9]+$'
      register: backup_dirs
      
    - name: Set backup directory to use
      set_fact:
        latest_backup: "{{ backup_dirs.files | sort(attribute='ctime', reverse=true) | first }}"
      when: backup_dirs.files | length > 0
      
    - name: Fail if no backup found
      fail:
        msg: "No backup found for rollback"
      when: backup_dirs.files | length == 0
  
  tasks:
    - name: Display rollback information
      debug:
        msg: |
          Rolling back to backup from: {{ latest_backup.path }}
          Backup created: {{ latest_backup.ctime }}
          Current time: {{ ansible_date_time.iso8601 }}
    
    - name: Stop JBoss service
      systemd:
        name: jboss
        state: stopped
      register: jboss_stop
      
    - name: Wait for JBoss to fully stop
      wait_for:
        port: 8080
        host: localhost
        state: stopped
        timeout: 60
    
    - name: Create emergency backup of current state
      copy:
        src: "{{ deploy_dir }}/{{ war_file }}"
        dest: "{{ backup_dir }}/emergency_backup_{{ ansible_date_time.epoch }}/{{ war_file }}"
        remote_src: yes
      ignore_errors: yes
        
    - name: Remove current deployment
      file:
        path: "{{ deploy_dir }}/{{ war_file }}"
        state: absent
        
    - name: Remove deployment markers
      file:
        path: "{{ deploy_dir }}/{{ war_file }}.deployed"
        state: absent
        
    - name: Restore previous version
      copy:
        src: "{{ latest_backup.path }}/{{ war_file }}"
        dest: "{{ deploy_dir }}/{{ war_file }}"
        remote_src: yes
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
        mode: '0644'
        
    - name: Restore previous configuration
      copy:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
        remote_src: yes
        owner: "{{ app_user }}"
        group: "{{ app_user }}"
        mode: '0644'
      loop:
        - { src: "{{ latest_backup.path }}/ngop.properties", dest: "{{ jboss_home }}/standalone/configuration/ngop.properties" }
        - { src: "{{ latest_backup.path }}/standalone.xml", dest: "{{ jboss_home }}/standalone/configuration/standalone.xml" }
      ignore_errors: yes  # Config files might not be in backup
        
    - name: Start JBoss service
      systemd:
        name: jboss
        state: started
      register: jboss_start
      
    - name: Wait for JBoss to start
      wait_for:
        port: 8080
        host: localhost
        state: started
        timeout: 300
        
    - name: Wait for application to be available
      uri:
        url: "http://localhost:8080/business-services/health"
        method: GET
        status_code: 200
      register: health_check
      until: health_check.status == 200
      retries: 30
      delay: 10
      
    - name: Verify rollback success
      uri:
        url: "http://localhost:8080/business-services/health"
        method: GET
        return_content: yes
      register: health_response
      
    - name: Display rollback status
      debug:
        msg: |
          Rollback Status: SUCCESS
          Restored Version: {{ health_response.json.version | default('Unknown') }}
          Rollback Time: {{ ansible_date_time.iso8601 }}
          Server: {{ inventory_hostname }}
          
  post_tasks:
    - name: Run post-rollback smoke tests
      include_tasks: smoke-tests.yml
      
    - name: Send rollback notification
      uri:
        url: "{{ slack_webhook_url }}"
        method: POST
        body_format: json
        body:
          text: "πŸ”„ Dione rolled back on {{ environment }} - {{ inventory_hostname }}"
      when: slack_webhook_url is defined

Emergency Rollback Script

emergency-rollback.sh
#!/bin/bash
# Emergency rollback script for critical production issues
# Usage: ./emergency-rollback.sh [environment]

set -e

ENVIRONMENT=${1:-production}
JBOSS_HOME="/opt/jboss"
DEPLOY_DIR="$JBOSS_HOME/standalone/deployments"
BACKUP_DIR="/opt/deployments/backups"
WAR_FILE="business-services-${ENVIRONMENT}.war"

echo "=== EMERGENCY ROLLBACK INITIATED ==="
echo "Environment: $ENVIRONMENT"
echo "Timestamp: $(date)"
echo "User: $(whoami)"

# Verify we're running as the correct user
if [ "$(whoami)" != "jboss" ]; then
    echo "ERROR: Must run as jboss user"
    exit 1
fi

# Find the latest backup
LATEST_BACKUP=$(find $BACKUP_DIR -maxdepth 1 -type d -name "[0-9]*" | sort -nr | head -1)

if [ -z "$LATEST_BACKUP" ]; then
    echo "ERROR: No backup found"
    exit 1
fi

echo "Using backup: $LATEST_BACKUP"
echo "Backup created: $(stat -c %y $LATEST_BACKUP)"

# Confirm rollback
if [ "$ENVIRONMENT" = "production" ]; then
    echo ""
    echo "WARNING: This will rollback PRODUCTION!"
    read -p "Type 'rollback' to continue: " CONFIRM
    
    if [ "$CONFIRM" != "rollback" ]; then
        echo "Rollback cancelled"
        exit 1
    fi
fi

echo "Starting rollback process..."

# Stop JBoss
echo "Stopping JBoss..."
sudo systemctl stop jboss

# Wait for JBoss to stop
echo "Waiting for JBoss to stop..."
sleep 10

# Check if JBoss is really stopped
if pgrep -f jboss > /dev/null; then
    echo "Force killing JBoss processes..."
    sudo pkill -f jboss
    sleep 5
fi

# Create emergency backup of current state
EMERGENCY_BACKUP="$BACKUP_DIR/emergency_$(date +%s)"
mkdir -p "$EMERGENCY_BACKUP"

if [ -f "$DEPLOY_DIR/$WAR_FILE" ]; then
    echo "Creating emergency backup..."
    cp "$DEPLOY_DIR/$WAR_FILE" "$EMERGENCY_BACKUP/"
fi

# Remove current deployment
echo "Removing current deployment..."
rm -f "$DEPLOY_DIR/$WAR_FILE"
rm -f "$DEPLOY_DIR/$WAR_FILE.deployed"
rm -f "$DEPLOY_DIR/$WAR_FILE.failed"

# Restore from backup
echo "Restoring from backup..."
if [ ! -f "$LATEST_BACKUP/$WAR_FILE" ]; then
    echo "ERROR: WAR file not found in backup: $LATEST_BACKUP/$WAR_FILE"
    exit 1
fi

cp "$LATEST_BACKUP/$WAR_FILE" "$DEPLOY_DIR/"

# Start JBoss
echo "Starting JBoss..."
sudo systemctl start jboss

# Wait for JBoss to start
echo "Waiting for JBoss to start..."
for i in {1..60}; do
    if curl -f http://localhost:8080/business-services/health > /dev/null 2>&1; then
        echo "JBoss started successfully"
        break
    fi
    echo "Waiting... ($i/60)"
    sleep 5
done

# Verify application is working
if curl -f http://localhost:8080/business-services/health > /dev/null 2>&1; then
    echo "=== ROLLBACK SUCCESSFUL ==="
    echo "Application is responding"
    
    # Send notification
    if [ -n "$SLACK_WEBHOOK" ]; then
        curl -X POST -H 'Content-type: application/json' \
             --data '{"text":"πŸ”„ EMERGENCY ROLLBACK completed on '$(hostname)' - '$(date)'"}' \
             "$SLACK_WEBHOOK"
    fi
else
    echo "=== ROLLBACK FAILED ==="
    echo "Application is not responding"
    exit 1
fi

Deployment Monitoring

Monitoring deployment health and application performance after releases.

Health Check Endpoints

HealthCheckController.java
@RestController
@RequestMapping("/health")
public class HealthCheckController {
    
    @Autowired
    private DataSource dataSource;
    
    @Autowired
    private CacheDataConsumer cacheDataConsumer;
    
    @Value("${build.version:unknown}")
    private String buildVersion;
    
    @Value("${build.timestamp:unknown}")
    private String buildTimestamp;
    
    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity> health() {
        Map health = new HashMap<>();
        
        try {
            // Basic application info
            health.put("status", "UP");
            health.put("timestamp", new Date());
            health.put("version", buildVersion);
            health.put("buildTime", buildTimestamp);
            health.put("hostname", InetAddress.getLocalHost().getHostName());
            
            // Component health checks
            health.put("database", checkDatabaseHealth());
            health.put("cache", checkCacheHealth());
            health.put("externalServices", checkExternalServices());
            
            // Performance metrics
            health.put("memory", getMemoryInfo());
            health.put("threads", getThreadInfo());
            
            return ResponseEntity.ok(health);
            
        } catch (Exception e) {
            health.put("status", "DOWN");
            health.put("error", e.getMessage());
            return ResponseEntity.status(503).body(health);
        }
    }
    
    private Map checkDatabaseHealth() {
        Map dbHealth = new HashMap<>();
        
        try {
            try (Connection conn = dataSource.getConnection()) {
                // Test basic connectivity
                boolean isValid = conn.isValid(5);
                
                if (isValid) {
                    // Test query execution
                    try (PreparedStatement stmt = conn.prepareStatement("SELECT COUNT(*) FROM organizations")) {
                        ResultSet rs = stmt.executeQuery();
                        rs.next();
                        int orgCount = rs.getInt(1);
                        
                        dbHealth.put("status", "UP");
                        dbHealth.put("organizationCount", orgCount);
                        dbHealth.put("responseTime", "< 100ms");
                    }
                } else {
                    dbHealth.put("status", "DOWN");
                    dbHealth.put("error", "Connection validation failed");
                }
            }
        } catch (Exception e) {
            dbHealth.put("status", "DOWN");
            dbHealth.put("error", e.getMessage());
        }
        
        return dbHealth;
    }
    
    private Map checkCacheHealth() {
        Map cacheHealth = new HashMap<>();
        
        try {
            // Test cache operations
            String testKey = "health_check_" + System.currentTimeMillis();
            String testValue = "test_value";
            
            // Test put
            cacheDataConsumer.put(testKey, testValue, 60);
            
            // Test get
            String retrievedValue = (String) cacheDataConsumer.get(testKey);
            
            if (testValue.equals(retrievedValue)) {
                cacheHealth.put("status", "UP");
                cacheHealth.put("responseTime", "< 50ms");
            } else {
                cacheHealth.put("status", "DOWN");
                cacheHealth.put("error", "Cache value mismatch");
            }
            
            // Clean up test key
            cacheDataConsumer.remove(testKey);
            
        } catch (Exception e) {
            cacheHealth.put("status", "DOWN");
            cacheHealth.put("error", e.getMessage());
        }
        
        return cacheHealth;
    }
    
    private Map checkExternalServices() {
        Map externalHealth = new HashMap<>();
        
        // Check Pricing Engine
        externalHealth.put("pricingEngine", checkPricingEngineHealth());
        
        // Check Salesforce
        externalHealth.put("salesforce", checkSalesforceHealth());
        
        return externalHealth;
    }
    
    private Map getMemoryInfo() {
        Runtime runtime = Runtime.getRuntime();
        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        long usedMemory = totalMemory - freeMemory;
        
        Map memory = new HashMap<>();
        memory.put("total", totalMemory / 1024 / 1024 + "MB");
        memory.put("used", usedMemory / 1024 / 1024 + "MB");
        memory.put("free", freeMemory / 1024 / 1024 + "MB");
        memory.put("usagePercentage", (usedMemory * 100) / totalMemory + "%");
        
        return memory;
    }
}

Deployment Monitoring Script

monitor-deployment.sh
#!/bin/bash
# Post-deployment monitoring script

ENVIRONMENT=$1
HEALTH_URL="http://localhost:8080/business-services/health"
MONITORING_DURATION=300  # 5 minutes
CHECK_INTERVAL=10        # 10 seconds

echo "Starting post-deployment monitoring for $ENVIRONMENT"
echo "Duration: ${MONITORING_DURATION}s, Interval: ${CHECK_INTERVAL}s"

START_TIME=$(date +%s)
SUCCESS_COUNT=0
FAILURE_COUNT=0
RESPONSE_TIMES=()

while [ $(($(date +%s) - START_TIME)) -lt $MONITORING_DURATION ]; do
    
    # Perform health check
    START_CHECK=$(date +%s%3N)
    
    if RESPONSE=$(curl -s -w "%{http_code}" -o /tmp/health_response.json "$HEALTH_URL" 2>/dev/null); then
        HTTP_CODE="${RESPONSE: -3}"
        END_CHECK=$(date +%s%3N)
        RESPONSE_TIME=$((END_CHECK - START_CHECK))
        
        if [ "$HTTP_CODE" = "200" ]; then
            SUCCESS_COUNT=$((SUCCESS_COUNT + 1))
            RESPONSE_TIMES+=($RESPONSE_TIME)
            
            # Extract key metrics from response
            VERSION=$(jq -r '.version // "unknown"' /tmp/health_response.json 2>/dev/null)
            DB_STATUS=$(jq -r '.database.status // "unknown"' /tmp/health_response.json 2>/dev/null)
            CACHE_STATUS=$(jq -r '.cache.status // "unknown"' /tmp/health_response.json 2>/dev/null)
            
            echo "[$(date '+%H:%M:%S')] βœ… Health check passed (${RESPONSE_TIME}ms) - DB:$DB_STATUS Cache:$CACHE_STATUS Version:$VERSION"
            
        else
            FAILURE_COUNT=$((FAILURE_COUNT + 1))
            echo "[$(date '+%H:%M:%S')] ❌ Health check failed - HTTP $HTTP_CODE"
        fi
    else
        FAILURE_COUNT=$((FAILURE_COUNT + 1))
        echo "[$(date '+%H:%M:%S')] ❌ Health check failed - Connection error"
    fi
    
    sleep $CHECK_INTERVAL
done

# Calculate statistics
TOTAL_CHECKS=$((SUCCESS_COUNT + FAILURE_COUNT))
SUCCESS_RATE=$(echo "scale=2; $SUCCESS_COUNT * 100 / $TOTAL_CHECKS" | bc)

if [ ${#RESPONSE_TIMES[@]} -gt 0 ]; then
    # Calculate average response time
    TOTAL_TIME=0
    for time in "${RESPONSE_TIMES[@]}"; do
        TOTAL_TIME=$((TOTAL_TIME + time))
    done
    AVG_RESPONSE_TIME=$(echo "scale=2; $TOTAL_TIME / ${#RESPONSE_TIMES[@]}" | bc)
else
    AVG_RESPONSE_TIME="N/A"
fi

echo ""
echo "=== DEPLOYMENT MONITORING SUMMARY ==="
echo "Environment: $ENVIRONMENT"
echo "Duration: ${MONITORING_DURATION}s"
echo "Total Checks: $TOTAL_CHECKS"
echo "Successful: $SUCCESS_COUNT"
echo "Failed: $FAILURE_COUNT"
echo "Success Rate: ${SUCCESS_RATE}%"
echo "Average Response Time: ${AVG_RESPONSE_TIME}ms"

# Determine overall health
if (( $(echo "$SUCCESS_RATE >= 95" | bc -l) )); then
    echo "Status: βœ… HEALTHY"
    exit 0
elif (( $(echo "$SUCCESS_RATE >= 80" | bc -l) )); then
    echo "Status: ⚠️ DEGRADED"
    exit 1
else
    echo "Status: ❌ UNHEALTHY"
    exit 2
fi

🎯 Deployment Best Practices

  • Safety First: Multiple approval gates for production deployments
  • Automated Testing: Comprehensive test suite before deployment
  • Blue-Green Strategy: Deploy to one server at a time
  • Rollback Plan: Always have a tested rollback procedure
  • Monitoring: Monitor application health after deployment
  • Communication: Clear notifications to stakeholders