잊지 않겠습니다.

Gradle + yo deploy script

gradle을 이용한 build script 만들기 재미가 붙어, 이번에는 yo로 만든 web application을 auto-deploy를 지원하는 script를 한번 작성해봤습니다.

지원하는 시나리오는 다음과 같습니다.

  1. 이번에 작성한 2개의 public web과 mobile web을 모두 사용 가능
  2. 다음과 같은 process를 진행 가능해야지 됨
  • grunt build
  • svn에 새로 deploy할 web application의 압축본을 add & commit
  • ftp를 이용한 web application 배포

먼저, 여러 web application을 지원하기 위한 조건은 외부 parameter를 받는 방법이 가장 좋아서 target을 지정하기로 했습니다.

작성된 web aplication들의 구조는 다음과 같습니다.

.
├── admin-web
├── mobile-web
├── public-web
└── sample-web

여기서 sample-web의 경우에는 팀원들에게 같이 공유할 sample web application이기 때문에 배포 대상이 되지 못하고, admin, mobile, public의 경우에는 배포 대상이 될 수 있습니다. 간단히 이름을 기준으로 admin, mobile, public을 기준으로 삼고, 이를 parameter로 받으면 될 수 있다는 판단이 내려졌습니다.

이제 다음 조건들이 필요합니다.

  • grunt,svn 과 같은 command의 실행
  • grunt deploy가 된 후에 zip compress
  • ftp를 이용한 data copy

gradle을 이용한 command의 실행

기본적으로 gradle을 이용한 command는 type을 Exec로 잡아주면 처리가 가능합니다. 이와 같은 sub directory 내에서 실행되어야지 될 command는 workingDir 값을 지정해줘서 처리가 가능합니다. 다음은 grunt build를 하는 code입니다.

task build(type: Exec) {
    workingDir getWebDir()
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine 'cmd', '/c', 'grunt', 'build'
    } else {
        commandLine 'grunt', 'build'
    }
    ext.output = {
        return standardOutput.toString()
    }
}

윈도우즈 계통과 linux/mac을 모두 지원하기 위해서 OS의 type을 설정해줘야지 됩니다. windows에서 cmd와 /c를 이용하는 것만을 주의하면 매우 간단한 코드입니다.

zip 파일 압축

zip 압축은 gradle에서 기본으로 제공하고 있는 Zip type을 이용하면 됩니다. zip type의 경우, from과 destinationDir만을 주의해서 처리하면 됩니다. 다음은 만들어진 distPath의 모든 내용을 yyyyMMddHHmm-hostname.zip 형식으로 압축을 하는 코드입니다.

task compressDist(type: Zip) {
    dependsOn 'build'

    from file(getDistPath())
    destinationDir file(deployPath)

    def now = new Date()
    def dateFormat = new SimpleDateFormat("yyyyMMddHHmm");
    def hostname = InetAddress.getLocalHost().getHostName().toLowerCase()

    filename = String.format("%s-%s.zip", dateFormat.format(now), hostname)
    archiveName filename
}

FTP 파일 전송

gradle은 자체적으로 FTP를 지원하지 않습니다. 다만 ant 를 지원하고 있기 때문에, ant의 FTP를 이용하면 됩니다. 그런데 ant ftp의 경우에는 send시에 hang이 걸리는 버그를 가지고 있습니다. passive mode를 true로 설정하는 것으로 hang이 걸리는 경우를 조금 덜 하게 할 수 있긴 하지만, 그래도 send에서 hang이 걸리는 것을 완벽하게 막지는 못합니다. 이 부분에 대해서는 좀 더 논의가 필요할 것 같습니다. 다음은 ftp 전송 코드입니다.

task 'upload' {
    dependsOn 'commitSvn'
    doLast {
        def remoteDir = getFtpPath()
        ant {
            taskdef(name: 'ftp', classname: 'org.apache.tools.ant.taskdefs.optional.net.FTP', classpath: configurations.ftpAntTask.asPath)
            ftp(action: 'mkdir', remotedir: remoteDir, server: ftpUrl, userid: ftpUsername, password: ftpPassword)
            ftp(action: 'delete', remotedir: remoteDir, server: ftpUrl, userid: ftpUsername, password: ftpPassword) {
                fileset() { include(name: '**/*') }
            }
            ftp(action: 'send', remotedir: remoteDir, verbose: true,
                    depends: true, binary: true, passive: true,
                    server: ftpUrl, userid: ftpUsername, password: ftpPassword) {
                fileset(dir: getDistPath()) {
                    include(name: '**/**/*')
                }
            }
        }
    }
}

각 build task에 대한 dependency를 추가하고, target argument를 받아서 처리하도록 코드 수정을 마져 완료한 최종 코드는 다음과 같습니다.

import org.apache.tools.ant.taskdefs.condition.Os

import java.text.SimpleDateFormat

configurations {
    ftpAntTask
}

repositories {
    mavenCentral()
}

dependencies {
    ftpAntTask("org.apache.ant:ant-commons-net:1.8.2") {
        module("commons-net:commons-net:1.4.1") {
            dependencies "oro:oro:2.0.8:jar"
        }
    }
}

def ftpUrl = '192.168.13.210'
def ftpUsername = 'ykyoon'
def ftpPassword = 'qwer12#$'
def filename = ''

def getDistPath() {
    return String.format('%s/dist', getWebDir())
}

def getWebDir() {
    return String.format('%s-web', project.target)
}

def getDeployPath() {
    return String.format('../deployed/%s-web/', project.target);
}

def getFtpPath() {
    return String.format('www-fms/%s', project.target);
}

task build(type: Exec) {
//    dependsOn 'upSvn'
    workingDir getWebDir()
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine 'cmd', '/c', 'grunt', 'build'
    } else {
        commandLine 'grunt', 'build'
    }
    ext.output = {
        return standardOutput.toString()
    }
}

task compressDist(type: Zip) {
    dependsOn 'build'

    from file(getDistPath())
    destinationDir file(deployPath)

    def now = new Date()
    def dateFormat = new SimpleDateFormat("yyyyMMddHHmm");
    def hostname = InetAddress.getLocalHost().getHostName().toLowerCase()

    filename = String.format("%s-%s.zip", dateFormat.format(now), hostname)
    archiveName filename
}

task upSvn(type: Exec) {
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine 'cmd', '/c', 'svn', 'up'
    } else {
        commandLine 'svn', 'up'
    }
    ext.output = {
        return standardOutput.toString()
    }
}

task addSvn(type: Exec) {
    dependsOn 'compressDist'

    String svnParam = getDeployPath() + filename
    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine 'cmd', '/c', 'svn', 'add', svnParam
    } else {
        commandLine 'svn', 'add', svnParam
    }
    ext.output = {
        return standardOutput.toString()
    }
}

task commitSvn(type: Exec) {
    dependsOn 'addSvn'
    String svnParam = getDeployPath() + filename
    String svnLog = '-mCommit file before deployed'

    if (Os.isFamily(Os.FAMILY_WINDOWS)) {
        commandLine 'cmd', '/c', 'svn', 'add', svnParam, svnLog
    } else {
        commandLine 'svn', 'commit', svnParam, svnLog
    }
    ext.output = {
        return standardOutput.toString()
    }
}

task 'upload' {
    dependsOn 'commitSvn'
    doLast {
        def remoteDir = getFtpPath()
        ant {
            taskdef(name: 'ftp', classname: 'org.apache.tools.ant.taskdefs.optional.net.FTP', classpath: configurations.ftpAntTask.asPath)
            ftp(action: 'mkdir', remotedir: remoteDir, server: ftpUrl, userid: ftpUsername, password: ftpPassword)
            ftp(action: 'delete', remotedir: remoteDir, server: ftpUrl, userid: ftpUsername, password: ftpPassword) {
                fileset() { include(name: '**/*') }
            }
            ftp(action: 'send', remotedir: remoteDir, verbose: true,
                    depends: true, binary: true, passive: true,
                    server: ftpUrl, userid: ftpUsername, password: ftpPassword) {
                fileset(dir: getDistPath()) {
                    include(name: '**/**/*')
                }
            }
        }
    }
}

task 'deploy' {
    dependsOn 'build'
    dependsOn 'upload'
    doLast {
        println 'grunt build and upload'
    }
}

task help << {
    println '--------- gradle web command help ----------------------'
    println 'gradle deploy -Ptarget=public : public web build'
    println 'gradle deploy -Ptarget=mobile : mobile web build'
    println '--------- end line -----------------------------------------'
}

모두들 즐거운 코딩 되세요. ^^

Posted by Y2K
,