All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.auth.profile.ProfilesConfigFileWriter Maven / Gradle / Ivy

Go to download

The AWS SDK for Java with support for OSGi. The AWS SDK for Java provides Java APIs for building software on AWS' cost-effective, scalable, and reliable infrastructure products. The AWS Java SDK allows developers to code against APIs for all of Amazon's infrastructure web services (Amazon S3, Amazon EC2, Amazon SQS, Amazon Relational Database Service, Amazon AutoScaling, etc).

There is a newer version: 1.11.60
Show newest version
/*
 * Copyright 2014-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. 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.auth.profile;

import com.amazonaws.AmazonClientException;
import com.amazonaws.auth.profile.internal.AbstractProfilesConfigFileScanner;
import com.amazonaws.auth.profile.internal.Profile;
import com.amazonaws.auth.profile.internal.ProfileKeyConstants;
import com.amazonaws.util.StringUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;
import java.util.UUID;

/**
 * The class for creating and modifying the credential profiles file.
 */
public class ProfilesConfigFileWriter {

    private static final Log LOG = LogFactory.getLog(ProfilesConfigFileWriter.class);

    /**
     * Write all the credential profiles to a file. Note that this method will
     * clobber the existing content in the destination file if it's in the
     * overwrite mode. Use {@link #modifyOrInsertProfiles(File, Profile...)}
     * instead, if you want to perform in-place modification on your existing
     * credentials file.
     *
     * @param destination
     *            The destination file where the credentials will be written to.
     * @param overwrite
     *            If true, this method If false, this method will throw
     *            exception if the file already exists.
     * @param profiles
     *            All the credential profiles to be written.
     */
    public static void dumpToFile(File destination, boolean overwrite, Profile... profiles) {
        if (destination.exists() && !overwrite) {
            throw new AmazonClientException(
                    "The destination file already exists. " +
                    "Set overwrite=true if you want to clobber the existing " +
                    "content and completely re-write the file.");
        }

        OutputStreamWriter writer;
        try {
            writer = new OutputStreamWriter(new FileOutputStream(destination, false), StringUtils.UTF8);

        } catch (IOException ioe) {
            throw new AmazonClientException(
                    "Unable to open the destination file.", ioe);
        }

        try {
            final Map modifications = new LinkedHashMap();
            for (Profile profile : profiles) {
                modifications.put(profile.getProfileName(), profile);
            }
            ProfilesConfigFileWriterHelper writerHelper = new ProfilesConfigFileWriterHelper(writer, modifications);

            writerHelper.writeWithoutExistingContent();
        } finally {
            try { writer.close(); } catch (IOException ioe) {}
        }

    }

    /**
     * Modify or insert new profiles into an existing credentials file by
     * in-place modification. Only the properties of the affected profiles will
     * be modified; all the unaffected profiles and comment lines will remain
     * the same. This method does not support renaming a profile.
     *
     * @param destination
     *            The destination file to modify
     * @param profiles
     *            All the credential profiles to be written.
     */
    public static void modifyOrInsertProfiles(File destination, Profile... profiles) {
        final Map modifications = new LinkedHashMap();
        for (Profile profile : profiles) {
            modifications.put(profile.getProfileName(), profile);
        }

        modifyProfiles(destination, modifications);
    }

    /**
     * Modify one profile in the existing credentials file by in-place
     * modification. This method will rename the existing profile if the
     * specified Profile has a different name.
     *
     * @param destination
     *            The destination file to modify
     * @param profileName
     *            The name of the existing profile to be modified
     * @param newProfile
     *            The new Profile object.
     */
    public static void modifyOneProfile(File destination, String profileName, Profile newProfile) {
        final Map modifications = Collections.singletonMap(profileName, newProfile);

        modifyProfiles(destination, modifications);
    }

    /**
     * Remove one or more profiles from the existing credentials file by
     * in-place modification.
     *
     * @param destination
     *            The destination file to modify
     * @param profileNames
     *            The names of all the profiles to be deleted.
     */
    public static void deleteProfiles(File destination, String... profileNames) {
        final Map modifications = new LinkedHashMap();
        for (String profileName : profileNames) {
            modifications.put(profileName, null); // null value indicates a deletion
        }

        modifyProfiles(destination, modifications);
    }

    /**
     * A package-private method that supports all kinds of profile modification,
     * including renaming or deleting one or more profiles.
     *
     * @param modifications
     *            Use null key value to indicate a profile that is to be
     *            deleted.
     */
    static void modifyProfiles(File destination, Map modifications) {
        final boolean inPlaceModify = destination.exists();
        File stashLocation = null;

        // Stash the original file, before we apply the changes
        if (inPlaceModify) {
            boolean stashed = false;

            try {
                // We can't use File.createTempFile, since it will always create
                // that file no matter what, and File.reNameTo does not allow
                // the destination to be an existing file
                stashLocation = new File(destination.getParentFile(),
                        destination.getName() + ".bak."
                                + UUID.randomUUID().toString());
                stashed = destination.renameTo(stashLocation);

                if (LOG.isDebugEnabled()) {
                    LOG.debug(String
                            .format("The original credentials file is stashed to loaction (%s).",
                                    stashLocation.getAbsolutePath()));
                }

            } finally {
                if (!stashed) {
                    throw new AmazonClientException(
                            "Failed to stash the existing credentials file " +
                            "before applying the changes.");
                }
            }
        }

        OutputStreamWriter writer = null;
        try {
            writer = new OutputStreamWriter(new FileOutputStream(destination), StringUtils.UTF8);
            ProfilesConfigFileWriterHelper writerHelper = new ProfilesConfigFileWriterHelper(writer, modifications);

            if (inPlaceModify) {
                Scanner existingContent = new Scanner(stashLocation, StringUtils.UTF8.name());
                writerHelper.writeWithExistingContent(existingContent);
            } else {
                writerHelper.writeWithoutExistingContent();
            }

            // Make sure the output is valid and can be loaded by the loader
            new ProfilesConfigFile(destination);

            if ( inPlaceModify && !stashLocation.delete() ) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug(String
                            .format("Successfully modified the credentials file. But failed to " +
                                    "delete the stashed copy of the original file (%s).",
                                    stashLocation.getAbsolutePath()));
                }
            }

        } catch (Exception e) {
            // Restore the stashed file
            if (inPlaceModify) {
                boolean restored = false;

                try {
                    // We don't really care about what destination.delete()
                    // returns, since the file might not have been created when
                    // the error occurred.
                    if ( !destination.delete() ) {
                        LOG.debug("Unable to remove the credentials file "
                                + "before restoring the original one.");
                    }
                    restored = stashLocation.renameTo(destination);
                } finally {
                    if (!restored) {
                        throw new AmazonClientException(
                                "Unable to restore the original credentials file. " +
                                "File content stashed in " + stashLocation.getAbsolutePath());
                    }
                }
            }

            throw new AmazonClientException(
                    "Unable to modify the credentials file. " +
                    "(The original file has been restored.)",
                    e);

        } finally {
            try {
                if (writer != null) writer.close();
            } catch (IOException e) {}
        }
    }

    /**
     * Implementation of AbstractProfilesConfigFileScanner, which reads the
     * content from an existing credentials file (if any) and then modifies some
     * of the profile properties in place.
     */
    private static class ProfilesConfigFileWriterHelper extends AbstractProfilesConfigFileScanner {

        /** The writer where the modified profiles will be output to */
        private final Writer writer;

        /** Map of all the profiles to be modified, keyed by profile names */
        private final Map newProfiles = new LinkedHashMap();

        /** Map of the names of all the profiles to be deleted */
        private final Set deletedProfiles= new HashSet();

        private final StringBuilder buffer = new StringBuilder();
        private final Map> existingProfileProperties = new HashMap>();

        /**
         * Creates ProfilesConfigFileWriterHelper with the specified new
         * profiles.
         *
         * @param writer
         *            The writer where the modified content is output to.
         * @param modifications
         *            A map of all the new profiles, keyed by the profile name.
         *            If a profile name is associated with a null value, it's
         *            profile content will be removed.
         */
        public ProfilesConfigFileWriterHelper(Writer writer, Map modifications) {
            this.writer = writer;

            for (Entry entry : modifications.entrySet()) {
                String profileName = entry.getKey();
                Profile profile    = entry.getValue();

                if (profile == null) {
                    deletedProfiles.add(profileName);
                } else {
                    newProfiles.put(profileName, profile);
                }
            }
        }

        /**
         * Append the new profiles to the writer, by reading from empty content.
         */
        public void writeWithoutExistingContent() {
            buffer.setLength(0);
            existingProfileProperties.clear();

            // Use empty String as input, since we are bootstrapping a new file.
            run(new Scanner(""));
        }

        /**
         * Read the existing content of a credentials file, and then make
         * in-place modification according to the new profiles specified in this
         * class.
         */
        public void writeWithExistingContent(Scanner existingContent) {
            buffer.setLength(0);
            existingProfileProperties.clear();

            run(existingContent);
        }

        @Override
        protected void onEmptyOrCommentLine(String profileName, String line) {
            /*
             * Buffer the line until we reach the next property line or the end
             * of the profile. We do this so that new properties could be
             * inserted at more appropriate location. For example:
             *
             * [default]
             * # access key
             * aws_access_key_id=aaa
             * # secret key
             * aws_secret_access_key=sss
             * # We want new properties to be inserted before this line
             * # instead of after the following empty line
             *
             * [next profile]
             * ...
             */
            if (profileName == null || !deletedProfiles.contains(profileName)) {
                buffer(line);
            }
        }

        @Override
        protected void onProfileStartingLine(String profileName, String line) {
            existingProfileProperties.put(profileName, new HashSet());

            // Copy the line after flush the buffer
            flush();

            if (deletedProfiles.contains(profileName))
                return;

            // If the profile name is changed
            if (newProfiles.get(profileName) != null) {
                String newProfileName = newProfiles.get(profileName).getProfileName();
                if ( !newProfileName.equals(profileName) ) {
                    line = "[" + newProfileName + "]";
                }
            }

            writeLine(line);
        }

        @Override
        protected void onProfileEndingLine(String prevProfileName) {
            // Check whether we need to insert new properties into this profile
            Profile modifiedProfile = newProfiles.get(prevProfileName);
            if (modifiedProfile != null) {
                for (Entry entry : modifiedProfile.getProperties().entrySet()) {
                    String propertyKey   = entry.getKey();
                    String propertyValue = entry.getValue();
                    if ( !existingProfileProperties.get(prevProfileName).contains(propertyKey) ) {
                        writeProperty(propertyKey, propertyValue);
                    }
                }
            }

            // flush all the buffered comments and empty lines
            flush();
        }

        @Override
        protected void onProfileProperty(String profileName,
                String propertyKey, String propertyValue,
                boolean isSupportedProperty, String line) {
            // Record that this property key has been declared for this profile
            if (existingProfileProperties.get(profileName) == null) {
                existingProfileProperties.put(profileName, new HashSet());
            }
            existingProfileProperties.get(profileName).add(propertyKey);

            if (deletedProfiles.contains(profileName))
                return;

            // Keep the unsupported properties
            if ( !isSupportedProperty ) {
                writeLine(line);
                return;
            }

            // flush all the buffered comments and empty lines before this property line
            flush();

            // Modify the property value
            if (newProfiles.containsKey(profileName)) {
                String newValue = newProfiles.get(profileName)
                        .getPropertyValue(propertyKey);
                if (newValue != null) {
                    writeProperty(propertyKey, newValue);
                }
                // else remove that line
            } else {
                writeLine(line);
            }

        }

        @Override
        protected void onEndOfFile() {
            // Append profiles that don't exist in the original file
            for (Entry entry : newProfiles.entrySet()) {
                String profileName = entry.getKey();
                Profile profile    = entry.getValue();

                if ( !existingProfileProperties.containsKey(profileName) ) {
                    // The profile name is not found in the file
                    // Append the profile properties
                    writeProfile(profile);
                    writeLine("");
                }
            }

            // Flush the "real" writer
            try {
                writer.flush();
            } catch (IOException ioe) {
                throw new AmazonClientException(
                        "Unable to write to the target file to persist the profile credentials.",
                        ioe);
            }
        }

        /**
         * ProfilesConfigFileWriter still deals with legacy {@link Profile} interface so it can only
         * modify credential related properties. All other properties should be preserved when
         * modifying profiles.
         */
        @Override
        protected boolean isSupportedProperty(String propertyName) {
            return ProfileKeyConstants.AWS_ACCESS_KEY_ID.equals(propertyName) ||
                   ProfileKeyConstants.AWS_SECRET_ACCESS_KEY.equals(propertyName) ||
                   ProfileKeyConstants.AWS_SESSION_TOKEN.equals(propertyName) ||
                   ProfileKeyConstants.EXTERNAL_ID.equals(propertyName) ||
                   ProfileKeyConstants.ROLE_ARN.equals(propertyName) ||
                   ProfileKeyConstants.ROLE_SESSION_NAME.equals(propertyName) ||
                   ProfileKeyConstants.SOURCE_PROFILE.equals(propertyName);
        }

        /* Private interface */

        private void writeProfile(Profile profile) {
            writeProfileName(profile.getProfileName());

            for (Entry entry : profile.getProperties().entrySet()) {
                writeProperty(entry.getKey(), entry.getValue());
            }
        }

        private void writeProfileName(String profileName) {
            writeLine(String.format("[%s]", profileName));
        }

        private void writeProperty(String propertyKey, String propertyValue) {
            writeLine(String.format("%s=%s", propertyKey, propertyValue));
        }

        private void writeLine(String line) {
            append(String.format("%s%n", line));
        }

        /**
         * This method handles IOException that occurs when calling the append
         * method on the writer.
         */
        private void append(String str) {
            try {
                writer.append(str);
            } catch (IOException ioe) {
                throw new AmazonClientException(
                        "Unable to write to the target file to persist the profile credentials.",
                        ioe);
            }
        }

        private void flush() {
            if (buffer.length() != 0) {
                append(buffer.toString());
                buffer.setLength(0);
            }
        }

        private void buffer(String line) {
            buffer.append(String.format("%s%n", line));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy