Groovy Tutorial(10)實作篇:製作 Amazon S3 檔案上傳工具
Groovy Tutorial(9)使用 Geb 開發 Web Test 網站自動化測試(下) << 前情 使用 Groovy 可以讓 Java 開發人員更方便撰寫工具程式,使用熟悉的語法與函式庫,許多我們利用 Shell Script 處理的事情,也能利用 Groovy Script 簡單完成任務。本篇介紹如何用 Groovy 製作檔案上傳工具程式,將指定的資料夾壓縮成 ZIP 檔案,然後備份到 Amazon S3 雲端儲存服務。 學習目標:
Amazon S3 儲存服務介紹S3(Simple Storage Service)是 AWS(Amazon Web Services)提供的雲端儲存服務,價格便宜且服務穩定度高,應用範圍也十分廣泛。使用 Amazon S3 服務的好處有:
放在 Dropbox 雲端硬碟的檔案,實際上就是儲存在 Amazon S3 服務。 對於 Java 應用程式的開發,可以使用 JetS3t 與官方的 AWS SDK for Java 兩種函式庫存取 S3 服務,本文中我們將以 AWS SDK 為例,它可以提供較完整的功能,除了可以存取 S3 服務外,日後也能用來存取其他 AWS 服務。 開始撰寫 Groovy Script 程式我們打算以 Groovy Script 的方式撰寫,這種方法更適合開發此類小工具;傳統 Java 程式需要大費周章建立專案,然後配置 Maven / Gradle 的專案建置設定,最後打包成 .jar 檔案才能方便執行;如此會增加太多不必要的成本,Groovy 讓我們可以用更簡單的方式完成任務。 我們只需要一個命名為 建立 #!/usr/bin/env groovy @Grab('com.amazonaws:aws-java-sdk:1.8.2') import com.amazonaws.auth.* import com.amazonaws.services.s3.* // 以下兩行需要替換成正確的 AWS Access Key ID 與 Secret Access Key String accessKey = "AWS_ACCESS_KEY_ID" String secretKey = "AWS_SECRET_ACCESS_KEY" def credentials = new BasicAWSCredentials(accessKey, secretKey) def s3client = new AmazonS3Client(credentials) 第一行的 在 Unix-like(如 Linux 或 Mac OS X)環境中,Shell Script 程式的第一行會使用
如果我們要知道 Groovy 安裝在哪裡,可以用 利用 chmod a+x s3upload ./s3upload 如果想讓寫好的 Groovy Script 能夠像一般系統指令同樣方便執行,可以將程式放在
如此一來不管工作目錄(current working directory)在哪個路徑,都能直接使用 註解:Windows 的開發者需要先安裝 Cygwin 軟體,才能模擬 Unix-like 的執行環境。 在這個程式中,我們利用 我們只要在已安裝 JDK 與 Groovy 軟體的電腦,就能夠直接執行此 Groovy Script 程式,程式內容也能夠隨時做修改,比起傳統用 Java 專案方式開發方便許多,而且所需要的函式庫檔案,也會在第一次執行時自動下載,未來如果需要使用更新版的函式庫,只要直接修改程式中的定義即可。 Groovy 讓開發者在 Shell Script 與 Java 程式之間獲得一個兩者優點兼顧的利益,可以讓工具程式像 Shell Script 一樣方便撰寫,又同時能直接存取 Java 豐富的資源,且可以使用已經熟悉的語法。 使用 Zip Task 壓縮檔案我們也可以呼叫系統的 使用 Groovy 的 AntBuilder 提供 ZipTask,只需要非常簡短的程式碼,就可以完成資料夾壓縮打包的任務。 new AntBuilder().zip( destFile: 'file1.zip', basedir: 'dir1' ) 由於這個工具產生的壓縮檔,只是先暫存在磁碟中,上傳完成後就會刪除,暫存檔要放在哪裡也是需要考慮的問題。一般 Shell Script 會將暫存檔放在 def zipFile = File.createTempFile("temp", ".zip") zipFile.delete() 自動產生的暫存檔位於哪個資料夾,會因不同作業系統而異,以下是在 Mac OS X 上產生的暫存檔路徑,路徑中包含隨機產生的字串,所以不會有命名重複衝突問題。
我們只需要暫存檔的路徑,但是 將以上程式碼組合在一起,就可以將資料夾壓縮保存至一個暫存檔。 def zipFile = File.createTempFile("temp", ".zip") zipFile.delete() new AntBuilder().zip( destFile: zipFile, basedir: 'dir1' ) 將檔案上傳到 S3 儲存Groovy Script 僅需要在檔案起始處設定 首先回顧本篇第一個範例程式,已經建立 @Grab('com.amazonaws:aws-java-sdk:1.8.2') import com.amazonaws.auth.* import com.amazonaws.services.s3.* // Skip... def credentials = new BasicAWSCredentials(accessKey, secretKey) def s3client = new AmazonS3Client(credentials) AWS SDK 的 API 操作,都是從建立 Crendentials 開始,註冊 AWS 服務後,即可從 AWS Management Console 取得授權的 Access Key ID 與 Secret Access Key,用這一組 Key 來產生 Groovy Script 程式很容易撰寫,新增或修改一個段落後,就可以立即執行、測試結果,即使只搭配 Vim 或 Sublime Text 等純文字編輯器,也可以很容易完成上百行的程式碼,不像傳統 Java 總是很依賴 IDE 開發工具;如果習慣有 IDE 豐富功能,也可以選擇支援 Groovy 的 IDE 工具如 IntelliJ IDEA。可以自由選擇用哪種方式撰寫程式,簡單易寫的 Groovy 讓開發者有更多選擇。 開始使用 S3 之前,需要先知道一些專用詞彙:
我們可以在 S3 建立多組 Bucket,分別給予不同的命名,對應不同檔案儲存的需求。查閱 AmazonS3Client 的 API 說明,可以開始在 Groovy Script 使用 API 提供的方法存取 S3 服務。 例如,列出所有的 Bucket 名稱。 s3client.listBuckets().each { println it.name } 也可以檢查一個 Bucket 是否存在,如果不存在就新建一個。 def bucketName = 's3.groovy-tutorial.com' if (!s3client.doesBucketExist(bucketName)) { s3client.createBucket(bucketName) } 需要特別注意的地方,就是所有 S3 使用者共用相同的命名空間,所以 Bucket 的命名需要避免與他人重複,一般來說使用網址是個常見的做法,例如:「s3.hello-groovy.com」。利用網址當作 Bucket 命名的做法,也可以在日後搭配網域名稱 DNS 的 CNAME 設定,使用自訂的 S3 存取網址。 使用 // putObject(String bucketName, String key, File file) s3client.putObject(bucketName, zipFile.name, zipFile) 使用 ConfigSlurper 讀取設定檔在程式中我們用到許多參數設定,例如 accessKey、secretKey 及 bucketName 等,為了方便使用者自行調整參數設定,我們需要讓這些參數可以在程式外部被設定。 使用系統環境變數(environment variable)的做法很常見,例如在 Shell 環境中設定變數「AWS_ACCESS_KEY_ID」。 export AWS_ACCESS_KEY_ID=... Groovy Script 中可以使用 System.getenv("AWS_ACCESS_KEY_ID") 但是有些情況,我們希望提供一個實際存在的設定檔,方便使用者檢視與修改參數內容。Groovy 提供的 設定檔範例: aws { // comments here credentials { accessKey = "AWS_ACCESS_KEY_ID" secretKey = "AWS_SECRET_ACCESS_KEY" } s3 { bucket = 's3.groovy-tutorial.com' } } 利用 def configPath = "${System.properties.'user.home'}/.s3upload/config.groovy" def config = new ConfigSlurper().parse(new File(configPath).toURL()) String accessKey = config.aws.credentials.accessKey String secretKey = config.aws.credentials.secretKey String bucketName = config.aws.s3.bucket 如此一來,我們就不必將敏感的資料直接放在程式碼,作業系統的不同使用者,也可以分別擁有各自不同的設定內容。使用獨立的外部設定檔更加便利且提升安全性,Groovy 搭配 使用 CliBuilder 設計指令參數Groovy 提供
使用 def cli = new CliBuilder(usage: 's3upload [options] dir') cli.h args: 0, longOpt: 'help', 'print usage information' cli.bucket args: 1, argName: 'bucketName', 'use given bucket name' def options = cli.parse(args)
if (options.h) { cli.usage() System.exit(0) } if (options.bucket) { bucketName = options.bucket } if (options.arguments().size() == 0) { System.err.println 'No dir specified. Use -h to show usage messages.' System.exit(-1) } def targetDir = options.arguments().first() println "Processing target dir: ${targetDir}" if (!new File(targetDir).isDirectory()) { System.err.println "Target dir ${targetDir} is not valid." System.exit(-1) } 完整程式實作範例將前面的程式碼片段整合,就能完成一個具有壓縮及上傳檔案到 S3 功能的小工具,例如執行「s3upload dir1」就可以將資料夾「dir1」壓縮後,上傳到 S3 儲存為「dir1.zip」檔案保存。 使用 Groovy 製作以 Java 函式庫為基礎發展的工具程式,就像撰寫 Shell Script 一樣容易,而且完成的程式也可以與其他 Shell 工具指令搭配使用。學會利用 Groovy Script 解決問題,能夠讓 Java 開發者如虎添翼。 程式碼範例: #!/usr/bin/env groovy @Grab('com.amazonaws:aws-java-sdk:1.8.2') import com.amazonaws.auth.* import com.amazonaws.services.s3.* def configPath = "${System.properties.'user.home'}/.s3upload/config.groovy" if (!new File(configPath).exists()) { System.err.println("Configuration file ${configPath} not exists.") System.exit(-1) } def config = new ConfigSlurper().parse(new File(configPath).toURL()) String accessKey = config.aws.credentials.accessKey String secretKey = config.aws.credentials.secretKey String bucketName = config.aws.s3.bucket def cli = new CliBuilder(usage: 's3upload [options] dir') cli.h args: 0, longOpt: 'help', 'print usage information' cli.bucket args: 1, argName: 'bucketName', 'use given bucket name' def options = cli.parse(args) if (options.h) { cli.usage() System.exit(0) } if (options.bucket) { bucketName = options.bucket } if (options.arguments().size() == 0) { System.err.println 'No dir specified. Use -h to show usage messages.' System.exit(-1) } def targetDir = options.arguments().first() println "Processing target dir: ${targetDir}" if (!new File(targetDir).isDirectory()) { System.err.println "Target dir ${targetDir} is not valid." System.exit(-1) } def credentials = new BasicAWSCredentials(accessKey, secretKey) def s3client = new AmazonS3Client(credentials) def zipFile = File.createTempFile("temp", ".zip") zipFile.delete() new AntBuilder().zip(destFile: zipFile, basedir: targetDir) s3client.listBuckets().each { println it.name } if (!s3client.doesBucketExist(bucketName)) { s3client.createBucket(bucketName) } s3client.putObject(bucketName, "${new File(targetDir).name}.zip", zipFile) 設定檔範例: aws { credentials { accessKey = "AWS_ACCESS_KEY_ID" secretKey = "AWS_SECRET_ACCESS_KEY" } s3 { bucket = 's3.groovy-tutorial.com' } } |