/*
* Copyright 2010-2014 Amazon Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://aws.amazon.com/apache2.0
*
* This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and
* limitations under the License.
*/
package com.amazonaws.eclipse.core.accounts;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.profile.ProfilesConfigFile;
import com.amazonaws.auth.profile.ProfilesConfigFileWriter;
import com.amazonaws.auth.profile.internal.BasicProfile;
import com.amazonaws.auth.profile.internal.Profile;
import com.amazonaws.eclipse.core.AccountInfo;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.accounts.preferences.PluginPreferenceStoreAccountCredentialsConfiguration;
import com.amazonaws.eclipse.core.accounts.preferences.PluginPreferenceStoreAccountOptionalConfiguration;
import com.amazonaws.eclipse.core.accounts.profiles.SdkProfilesCredentialsConfiguration;
import com.amazonaws.eclipse.core.preferences.PreferenceConstants;
import com.amazonaws.eclipse.core.ui.preferences.AwsAccountPreferencePage;
import com.amazonaws.util.StringUtils;
/**
* A class that loads and returns all the configured AccountInfo. It's
* also responsible for hooking the AccountInfoChangeListener.
*/
public class AccountInfoProvider {
private final IPreferenceStore prefStore;
/**
* Loading from the credentials file is an expensive operation, so we want
* to cache all the loaded AccountInfo objects.
*/
Map<String, AccountInfo> profileAccountInfoCache = new LinkedHashMap<String, AccountInfo>();
/** All the registered AccountInfoChangeListener */
List<AccountInfoChangeListener> listeners = new LinkedList<AccountInfoChangeListener>();
/**
* Initialize the provider with the given preference store instance.
*/
public AccountInfoProvider(IPreferenceStore prefStore) {
this.prefStore = prefStore;
}
/**
* Returns all the account info that are loaded from the credential profiles
* file. For performance reason, this method won't attempt to reload the
* accounts upon each method call, therefore the returned result might be
* out of sync with the external source. Call the {@link #refresh()} method
* to forcefully reload all the account info.
*
* @return Map from the accountId to each of the profile-based AccountInfo
* object.
*/
public Map<String, AccountInfo> getAllProfileAccountInfo() {
return Collections.unmodifiableMap(profileAccountInfoCache);
}
/**
* Returns the profile account info by the account identifier.
*
* @see #getAllProfileAccountInfo()
*/
public AccountInfo getProfileAccountInfo(String accountId) {
return profileAccountInfoCache.get(accountId);
}
/**
* Loads and returns all the legacy account configuration from the
* preference store system. The identifiers of legacy accounts could be
* found in the following preference keys:
* (1) accountIds (ids of all the global accounts)
* (2) accountIds-region (e.g. "accountIds-cn-north-1", ids of all the regional accounts)
*
* @return Map from the accountId to each of the legacy AccountInfo
* object.
*/
public Map<String, AccountInfo> getAllLegacyPreferenceStoreAccontInfo() {
Map<String, AccountInfo> preferenceStoreAccounts = new LinkedHashMap<String, AccountInfo>();
// Global accounts
preferenceStoreAccounts.putAll(loadPreferenceStoreAccountsByRegion(null));
// Regional accounts
List<String> regionsWithDefaultAccount = AwsAccountPreferencePage
.getRegionsWithDefaultAccounts(prefStore);
for (String regionId : regionsWithDefaultAccount) {
preferenceStoreAccounts.putAll(loadPreferenceStoreAccountsByRegion(regionId));
}
return preferenceStoreAccounts;
}
/**
* Returns the legacy preference store account info by the account
* identifier.
*
* @see #getAllLegacyPreferenceStoreAccontInfo()
*/
public AccountInfo getLegacyPreferenceStoreAccountInfo(String accountId) {
return getAllLegacyPreferenceStoreAccontInfo().get(accountId);
}
/**
* Forces the provider to refresh all the profile AccountInfo it vends.
*
* @param boostrapCredentialsFile
* If set true, this method will create a credentials file with a
* default profile if no account is currently configured in the
* toolkit.
* @param showWarningOnFailure
* If true, this method will show a warning message box if it
* fails to load accounts.
*/
public synchronized void refreshProfileAccountInfo(final boolean boostrapCredentialsFile, final boolean showWarningOnFailure) {
reloadProfileAccountInfo(boostrapCredentialsFile, showWarningOnFailure);
// Notify the change listeners
for (AccountInfoChangeListener listener : listeners) {
listener.onAccountInfoChange();
}
}
/**
* Update the following preference value:
* (1) P_CREDENTIAL_PROFILE_ACCOUNT_IDS - ids of all the accounts that were loaded from credential profiles file.
* (2) accountId:P_CREDENTIAL_PROFILE_NAME - name of the credential profile
*
* This method needs to be called whenever we reload profile credentials.
*/
public void updateProfileAccountMetadataInPreferenceStore(Collection<AccountInfo> accounts) {
List<String> accountIds = new LinkedList<String>();
for (AccountInfo accountInfo : accounts) {
accountIds.add(accountInfo.getInternalAccountId());
}
String accountIdsString = StringUtils.join(PreferenceConstants.ACCOUNT_ID_SEPARATOR,
accountIds.toArray(new String[accountIds.size()]));
prefStore.setValue(
PreferenceConstants.P_CREDENTIAL_PROFILE_ACCOUNT_IDS, accountIdsString);
for(AccountInfo profileAccount : accounts) {
String profileNamePrefKey = profileAccount.getInternalAccountId()
+ ":" + PreferenceConstants.P_CREDENTIAL_PROFILE_NAME;
prefStore.setValue(
profileNamePrefKey, profileAccount.getAccountName());
}
}
/**
* Register an AccountInfoChangeListener to this provider. All the
* registered listeners will be notified whenever this provider is
* refreshed.
*
* @param listener
* The new AccountInfoChangeListener to be registered.
*/
public void addAccountInfoChangeListener(AccountInfoChangeListener listener) {
synchronized(listeners) {
listeners.add(listener);
}
}
/**
* Remove an AccountInfoChangeListener from this provider.
*/
public void removeAccountInfoChangeListener(AccountInfoChangeListener listener) {
synchronized(listeners) {
listeners.remove(listener);
}
}
/* Profile-based account info */
private void reloadProfileAccountInfo(final boolean boostrapCredentialsFile, final boolean showWarningOnFailure) {
profileAccountInfoCache.clear();
/* Load the credential profiles file */
String credFileLocation = prefStore
.getString(PreferenceConstants.P_CREDENTIAL_PROFILE_FILE_LOCATION);
File credFile = new File(credFileLocation);
ProfilesConfigFile profileConfigFile = null;
try {
if (!credFile.exists() && boostrapCredentialsFile) {
ProfilesConfigFileWriter.dumpToFile(
credFile,
true, // overwrite=true
new Profile(PreferenceConstants.DEFAULT_ACCOUNT_NAME, new BasicAWSCredentials("", "")));
}
profileConfigFile = new ProfilesConfigFile(credFile);
} catch (Exception e) {
String errorMsg = String.format("Failed to load credential profiles from (%s).",
credFileLocation);
AwsToolkitCore.getDefault().logInfo(errorMsg + e.getMessage());
if ( showWarningOnFailure ) {
MessageDialog.openError(null,
"Unable to load profile accounts",
errorMsg + " Please check that your credentials file is at the correct location "
+ "and that it is in the correct format.");
}
}
if (profileConfigFile == null) return;
/* Set up the AccountInfo objects */
// Map from profile name to its pre-configured account id
Map<String, String> exisitingProfileAccountIds = getExistingProfileAccountIds();
// Iterate through the newly loaded profiles. Re-use the existing
// account id if the profile name is already configured in the toolkit.
// Otherwise assign a new UUID for it.
for (Entry<String, BasicProfile> entry : profileConfigFile.getAllBasicProfiles().entrySet()) {
String profileName = entry.getKey();
BasicProfile basicProfile = entry.getValue();
String accountId = exisitingProfileAccountIds.get(profileName);
if (accountId == null) {
AwsToolkitCore.getDefault().logInfo("No profile found: " + profileName);
accountId = UUID.randomUUID().toString();
}
AccountInfo profileAccountInfo = new AccountInfoImpl(accountId,
new SdkProfilesCredentialsConfiguration(prefStore, accountId, basicProfile),
// Profile accounts use profileName as the preference name prefix
// @see PluginPreferenceStoreAccountOptionalConfiguration
new PluginPreferenceStoreAccountOptionalConfiguration(prefStore, profileName));
profileAccountInfoCache.put(accountId, profileAccountInfo);
}
/* Update the preference store metadata for the newly loaded profile accounts */
updateProfileAccountMetadataInPreferenceStore(profileAccountInfoCache.values());
}
/**
* The toolkit uses the "credentialProfileAccountIds" preference property to
* store the identifiers of all the profile-based accounts. This method
* checks this preference value and returns a map of all the existing
* profile accounts that are already configured in the toolkit.
*
* @return Key - profile name; Value - account id
*/
private Map<String, String> getExistingProfileAccountIds() {
Map<String, String> exisitingProfileAccounts = new HashMap<String, String>();
// Ids of all the profile accounts currently configured in the toolkit
String[] profileAccountIds = prefStore.getString(
PreferenceConstants.P_CREDENTIAL_PROFILE_ACCOUNT_IDS).split(
PreferenceConstants.ACCOUNT_ID_SEPARATOR_REGEX);
for (String accountId : profileAccountIds) {
String configuredProfileName = prefStore.getString(
accountId + ":" + PreferenceConstants.P_CREDENTIAL_PROFILE_NAME);
if (configuredProfileName != null && !configuredProfileName.isEmpty()) {
exisitingProfileAccounts.put(configuredProfileName, accountId);
}
}
return exisitingProfileAccounts;
}
/* Pref-store-based account info */
/**
* Returns a map of all the preference-store-based account info.
*
* @param region
* Null value indicates the global region
*/
@SuppressWarnings("deprecation")
private Map<String, AccountInfo> loadPreferenceStoreAccountsByRegion(String regionId) {
String p_regionalAccountIds = regionId == null ?
PreferenceConstants.P_ACCOUNT_IDS
:
PreferenceConstants.P_ACCOUNT_IDS + "-" + regionId;
String[] accountIds = prefStore.getString(p_regionalAccountIds)
.split(PreferenceConstants.ACCOUNT_ID_SEPARATOR_REGEX);
Map<String, AccountInfo> accounts = new LinkedHashMap<String, AccountInfo>();
for ( String accountId : accountIds ) {
if (accountId.length() > 0) {
// Both credential and optional configurations are preference-store-based
AccountInfo prefStoreAccountInfo = new AccountInfoImpl(accountId,
new PluginPreferenceStoreAccountCredentialsConfiguration(prefStore, accountId),
new PluginPreferenceStoreAccountOptionalConfiguration(prefStore, accountId));
accounts.put(accountId, prefStoreAccountInfo);
}
}
return accounts;
}
}