/*
* Copyright (C) 2014-2015 Stichting Akvo (Akvo Foundation)
*
* This file is part of Akvo FLOW.
*
* Akvo FLOW is free software: you can redistribute it and modify it under the terms of
* the GNU Affero General Public License (AGPL) as published by the Free Software Foundation,
* either version 3 of the License or any later version.
*
* Akvo FLOW is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License included below for more details.
*
* The full license text can also be seen at <http://www.gnu.org/licenses/agpl.html>.
*/
package org.akvo.flow;
import java.io.File;
import java.io.FileWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.policy.Policy;
import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient;
import com.amazonaws.services.identitymanagement.model.AccessKey;
import com.amazonaws.services.identitymanagement.model.CreateAccessKeyRequest;
import com.amazonaws.services.identitymanagement.model.CreateAccessKeyResult;
import com.amazonaws.services.identitymanagement.model.CreateUserRequest;
import com.amazonaws.services.identitymanagement.model.GetUserRequest;
import com.amazonaws.services.identitymanagement.model.NoSuchEntityException;
import com.amazonaws.services.identitymanagement.model.PutUserPolicyRequest;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.Region;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
public class InstanceConfigurator {
private static final String GAE_SUFFIX = "-gae";
private static final String APK_SUFFIX = "-apk";
public static void main(String[] args) throws Exception {
Options opts = getOptions();
CommandLineParser parser = new DefaultParser();
CommandLine cli = null;
try {
cli = parser.parse(opts, args);
} catch (Exception e) {
System.err.println(e.getMessage());
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(InstanceConfigurator.class.getName(), opts);
System.exit(1);
}
String awsAccessKey = cli.getOptionValue("ak");
String awsSecret = cli.getOptionValue("as");
String bucketName = cli.getOptionValue("bn");
String gaeId = cli.getOptionValue("gae");
String outFolder = cli.getOptionValue("o");
String flowServices = cli.getOptionValue("fs");
String eventNotification = cli.getOptionValue("en");
String enableChangeEvents = cli.getOptionValue("ce", "false");
String mapsProvider = cli.getOptionValue("mapsProvider", "mapbox");
String googleMapsRegionBias = cli.getOptionValue("rb", "");
String cartodbApiKey = cli.getOptionValue("ck", "");
String cartodbSqlApi = cli.getOptionValue("cs", "");
String cartodbHost = cli.getOptionValue("ch", "");
String hereMapsAppId = cli.getOptionValue("hmai", "");
String hereMapsAppCode = cli.getOptionValue("hmac", "");
String alias = cli.getOptionValue("a");
String emailFrom = cli.getOptionValue("ef");
String emailTo = cli.getOptionValue("et");
String orgName = cli.getOptionValue("on");
String signingKey = cli.getOptionValue("sk");
File configFileHome = new File(outFolder);
if (! configFileHome.exists()) { configFileHome.mkdirs(); }
Map<String, AccessKey> accessKeys = new HashMap<String, AccessKey>();
String apiKey = UUID.randomUUID().toString().replaceAll("-", "");
AWSCredentials creds = new BasicAWSCredentials(awsAccessKey, awsSecret);
AmazonIdentityManagementClient iamClient = new AmazonIdentityManagementClient(creds);
AmazonS3Client s3Client = new AmazonS3Client(creds);
// Creating bucket
System.out.println("Creating bucket: " + bucketName);
try {
if (s3Client.doesBucketExist(bucketName)) {
System.out.println(bucketName
+ " already exists, skipping creation");
} else {
s3Client.createBucket(bucketName, Region.EU_Ireland);
}
} catch (Exception e) {
System.err.println("Error trying to create bucket " + bucketName
+ " : " + e.getMessage());
System.exit(1);
}
// Creating users and groups
String gaeUser = bucketName + GAE_SUFFIX;
String apkUser = bucketName + APK_SUFFIX;
// GAE
System.out.println("Creating user: " + gaeUser);
GetUserRequest gaeUserRequest = new GetUserRequest();
gaeUserRequest.setUserName(gaeUser);
try {
iamClient.getUser(gaeUserRequest);
System.out.println("User already exists, skipping creation");
} catch (NoSuchEntityException e) {
iamClient.createUser(new CreateUserRequest(gaeUser));
}
System.out.println("Requesting security credentials for " + gaeUser);
CreateAccessKeyRequest gaeAccessRequest = new CreateAccessKeyRequest();
gaeAccessRequest.setUserName(gaeUser);
CreateAccessKeyResult gaeAccessResult = iamClient.createAccessKey(gaeAccessRequest);
accessKeys.put(gaeUser, gaeAccessResult.getAccessKey());
// APK
System.out.println("Creating user: " + apkUser);
GetUserRequest apkUserRequest = new GetUserRequest();
apkUserRequest.setUserName(apkUser);
try {
iamClient.getUser(apkUserRequest);
System.out.println("User already exists, skipping creation");
} catch (NoSuchEntityException e) {
iamClient.createUser(new CreateUserRequest(apkUser));
}
System.out.println("Requesting security credentials for " + apkUser);
CreateAccessKeyRequest apkAccessRequest = new CreateAccessKeyRequest();
apkAccessRequest.setUserName(apkUser);
CreateAccessKeyResult apkAccessResult = iamClient
.createAccessKey(apkAccessRequest);
accessKeys.put(apkUser, apkAccessResult.getAccessKey());
System.out.println("Configuring security policies...");
Configuration cfg = new Configuration();
cfg.setClassForTemplateLoading(InstanceConfigurator.class, "/org/akvo/flow/templates");
cfg.setObjectWrapper(new DefaultObjectWrapper());
cfg.setDefaultEncoding("UTF-8");
Map<String, Object> data = new HashMap<String, Object>();
data.put("bucketName", bucketName);
data.put("version", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
data.put("accessKey", accessKeys);
Template t0 = cfg.getTemplate("bucket-policy.ftl");
StringWriter bucketPolicy = new StringWriter();
t0.process(data, bucketPolicy);
Template t1 = cfg.getTemplate("apk-s3-policy.ftl");
StringWriter apkPolicy = new StringWriter();
t1.process(data, apkPolicy);
Template t2 = cfg.getTemplate("gae-s3-policy.ftl");
StringWriter gaePolicy = new StringWriter();
t2.process(data, gaePolicy);
s3Client.setBucketPolicy(bucketName, bucketPolicy.toString());
iamClient.putUserPolicy(new PutUserPolicyRequest(apkUser, apkUser,
Policy.fromJson(apkPolicy.toString()).toJson()));
iamClient.putUserPolicy(new PutUserPolicyRequest(gaeUser, gaeUser,
Policy.fromJson(gaePolicy.toString()).toJson()));
System.out.println("Creating configuration files...");
// survey.properties
Map<String, Object> apkData = new HashMap<String, Object>();
apkData.put("awsBucket", bucketName);
apkData.put("awsAccessKeyId", accessKeys.get(apkUser).getAccessKeyId());
apkData.put("awsSecretKey", accessKeys.get(apkUser).getSecretAccessKey());
apkData.put("serverBase", "https://" + gaeId + ".appspot.com");
apkData.put("restApiKey", apiKey);
Template t3 = cfg.getTemplate("survey.properties.ftl");
t3.process(apkData, new FileWriter(new File(configFileHome, "/survey.properties")));
// appengine-web.xml
Map<String, Object> webData = new HashMap<String, Object>();
webData.put("awsBucket", bucketName);
webData.put("awsAccessKeyId", accessKeys.get(gaeUser).getAccessKeyId());
webData.put("awsSecretAccessKey", accessKeys.get(gaeUser).getSecretAccessKey());
webData.put("s3url", "https://" + bucketName + ".s3.amazonaws.com");
webData.put("instanceId", gaeId);
webData.put("alias", alias);
webData.put("flowServices", flowServices);
webData.put("eventNotification", eventNotification);
webData.put("enableChangeEvents", enableChangeEvents);
webData.put("mapsProvider", mapsProvider);
webData.put("googleMapsRegionBias", googleMapsRegionBias);
webData.put("cartodbApiKey", cartodbApiKey);
webData.put("cartodbSqlApi", cartodbSqlApi);
webData.put("cartodbHost", cartodbHost);
webData.put("hereMapsAppId", hereMapsAppId);
webData.put("hereMapsAppCode", hereMapsAppCode);
webData.put("apiKey", apiKey);
webData.put("emailFrom", emailFrom);
webData.put("emailTo", emailTo);
webData.put("organization", orgName);
webData.put("signingKey", signingKey);
Template t5 = cfg.getTemplate("appengine-web.xml.ftl");
t5.process(webData, new FileWriter(new File(configFileHome, "/appengine-web.xml")));
System.out.println("Done");
}
private static Options getOptions() {
Options options = new Options();
Option orgName = new Option("on", "Organzation name");
orgName.setLongOpt("orgName");
orgName.setArgs(1);
orgName.setRequired(true);
Option awsId = new Option("ak", "AWS Access Key");
awsId.setLongOpt("awsKey");
awsId.setArgs(1);
awsId.setRequired(true);
Option awsSecret = new Option("as", "AWS Access Secret");
awsSecret.setLongOpt("awsSecret");
awsSecret.setArgs(1);
awsSecret.setRequired(true);
Option bucketName = new Option("bn", "AWS S3 bucket name");
bucketName.setLongOpt("bucketName");
bucketName.setArgs(1);
bucketName.setRequired(true);
Option gaeServer = new Option("gae",
"GAE instance id - The `x` in https://x.appspot.com");
gaeServer.setLongOpt("gaeId");
gaeServer.setArgs(1);
gaeServer.setRequired(true);
Option emailFrom = new Option("ef",
"Sender email - NOTE: Must be developer in GAE instance");
emailFrom.setLongOpt("emailFrom");
emailFrom.setArgs(1);
emailFrom.setRequired(false);
Option emailTo = new Option("et",
"Recipient email of error notifications");
emailTo.setLongOpt("emailTo");
emailTo.setArgs(1);
emailTo.setRequired(true);
Option flowServices = new Option("fs",
"FLOW Services url, e.g. http://services.akvoflow.org");
flowServices.setLongOpt("flowServices");
flowServices.setArgs(1);
flowServices.setRequired(true);
Option eventNotification = new Option(
"en",
"FLOW Services event notification endpoint, e.g. http://services.akvoflow.org:3030/event_notification");
eventNotification.setLongOpt("eventNotification");
eventNotification.setArgs(1);
eventNotification.setRequired(true);
Option enableChangeEvents = new Option(
"ce",
"true if the instance should store change event data and notify FLOW services event notification endpoint of new events");
enableChangeEvents.setLongOpt("enableChangeEvents");
enableChangeEvents.setArgs(1);
enableChangeEvents.setRequired(false);
Option mapsProvider = new Option("mp",
"The maps provider to use. One of 'mapbox', 'google', 'cartodb'");
mapsProvider.setLongOpt("mapsProvider");
mapsProvider.setArgs(1);
mapsProvider.setRequired(false);
Option googleMapsRegionBias = new Option("rb",
"Region bias code (only available for google maps layers)");
googleMapsRegionBias.setLongOpt("googleMapsRegionBias");
googleMapsRegionBias.setArgs(1);
googleMapsRegionBias.setRequired(false);
Option cartodbApiKey = new Option("ck", "Cartodb api key");
cartodbApiKey.setLongOpt("cartodbApiKey");
cartodbApiKey.setArgs(1);
cartodbApiKey.setRequired(false);
Option cartodbSqlApi = new Option("cs",
"Url endpoint for the cartodb sql api");
cartodbSqlApi.setLongOpt("cartodbSqlApi");
cartodbSqlApi.setArgs(1);
cartodbSqlApi.setRequired(false);
Option cartodbHost = new Option("ch", "Cartodb host");
cartodbHost.setLongOpt("cartodbHost");
cartodbHost.setArgs(1);
cartodbHost.setRequired(false);
Option hereMapsAppId = new Option("hmai", "Here maps app id");
hereMapsAppId.setLongOpt("hereMapsAppId");
hereMapsAppId.setArgs(1);
hereMapsAppId.setRequired(false);
Option hereMapsAppCode = new Option("hmac", "Here maps app code");
hereMapsAppCode.setLongOpt("hereMapsAppCode");
hereMapsAppCode.setArgs(1);
hereMapsAppCode.setRequired(false);
Option outputFolder = new Option("o",
"Output folder for configuration files");
outputFolder.setLongOpt("outFolder");
outputFolder.setArgs(1);
outputFolder.setRequired(true);
Option alias = new Option("a",
"Instance alias, e.g. instance.akvoflow.org");
alias.setLongOpt("alias");
alias.setArgs(1);
alias.setRequired(true);
Option signingKey = new Option("sk", "Signing Key");
signingKey.setLongOpt("signingKey");
signingKey.setArgs(1);
signingKey.setRequired(true);
options.addOption(orgName);
options.addOption(awsId);
options.addOption(awsSecret);
options.addOption(bucketName);
options.addOption(gaeServer);
options.addOption(emailFrom);
options.addOption(emailTo);
options.addOption(outputFolder);
options.addOption(flowServices);
options.addOption(eventNotification);
options.addOption(enableChangeEvents);
options.addOption(mapsProvider);
options.addOption(googleMapsRegionBias);
options.addOption(cartodbApiKey);
options.addOption(cartodbSqlApi);
options.addOption(cartodbHost);
options.addOption(hereMapsAppId);
options.addOption(hereMapsAppCode);
options.addOption(alias);
options.addOption(signingKey);
return options;
}
}