/* * 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://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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. * * Contributions from 2013-2017 where performed either by US government * employees, or under US Veterans Health Administration contracts. * * US Veterans Health Administration contributions by government employees * are work of the U.S. Government and are not subject to copyright * protection in the United States. Portions contributed by government * employees are USGovWork (17USC ยง105). Not subject to copyright. * * Contribution by contractors to the US Veterans Health Administration * during this period are contractually contributed under the * Apache License, Version 2.0. * * See: https://www.usa.gov/government-works * * Contributions prior to 2013: * * Copyright (C) International Health Terminology Standards Development Organisation. * Licensed under the Apache License, Version 2.0. * */ package sh.isaac.mojo.profileSync; //~--- JDK imports ------------------------------------------------------------ import java.io.BufferedReader; import java.io.Console; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; //~--- non-JDK imports -------------------------------------------------------- import org.apache.commons.lang3.StringUtils; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Parameter; import sh.isaac.api.LookupService; import sh.isaac.api.sync.SyncFiles; import sh.isaac.mojo.external.QuasiMojo; //~--- classes ---------------------------------------------------------------- /** * {@link ProfilesMojoBase} * * This allows authentication to be passed in via system property, parameter, or, will * prompt for the username/password (if allowed by the system property 'profileSyncNoPrompt') * IN THAT ORDER. System properties have the highest priority. * * To prevent prompting during automated runs - set the system property 'profileSyncNoPrompt=true' * To set the username via system property - set 'profileSyncUsername=username' * To set the password via system property - set 'profileSyncPassword=password' * * To enable authentication without prompts, using public keys - set both of the following * 'profileSyncUsername=username' * 'profileSyncNoPrompt=true' * * This will cause a public key authentication to be attempted using the ssh credentials found * in the current users .ssh folder (in their home directory) * * @author <a href="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</a> */ public abstract class ProfilesMojoBase extends QuasiMojo { /** The Constant PROFILE_SYNC_DISABLE. */ // For disabling Profile Sync entirely public static final String PROFILE_SYNC_DISABLE = "profileSyncDisable"; /** The Constant PROFILE_SYNC_NO_PROMPTS. */ // For preventing command line prompts for credentials during automated runs - set this system property to true. public static final String PROFILE_SYNC_NO_PROMPTS = "profileSyncNoPrompt"; /** The Constant PROFILE_SYNC_USERNAME_PROPERTY. */ // Allow setting the username via a system property public static final String PROFILE_SYNC_USERNAME_PROPERTY = "profileSyncUsername"; /** The Constant PROFILE_SYNC_PWD_PROPERTY. */ // Allow setting the password via a system property public static final String PROFILE_SYNC_PWD_PROPERTY = "profileSyncPassword"; /** The username. */ private static String username = null; /** The pwd. */ private static char[] pwd = null; //~--- fields -------------------------------------------------------------- /** The disable hint given. */ private boolean disableHintGiven = false; /** * The location of the (already existing) profiles folder which should be shared via SCM. */ @Parameter(required = true) File userProfileFolderLocation = null; /** The location URL to use when connecting to the sync service. */ @Parameter(required = true) String changeSetURL = null; /** The Type of the specified changeSetURL - should be GIT or SVN. */ @Parameter(required = true) String changeSetURLType = null; /** The username to use for remote operations. */ @Parameter(required = false) private final String profileSyncUsername = null; /** The password to use for remote operations. */ @Parameter(required = false) private final String profileSyncPassword = null; //~--- constructors -------------------------------------------------------- /** * Instantiates a new profiles mojo base. * * @throws MojoExecutionException the mojo execution exception */ public ProfilesMojoBase() throws MojoExecutionException { super(); } //~--- methods ------------------------------------------------------------- /** * Execute. * * @throws MojoExecutionException the mojo execution exception * @see org.apache.maven.plugin.Mojo#execute() */ @Override public void execute() throws MojoExecutionException { if (StringUtils.isNotBlank(this.changeSetURL) && !this.changeSetURLType.equalsIgnoreCase("GIT") && !this.changeSetURLType.equalsIgnoreCase("SVN")) { throw new MojoExecutionException("Change set URL type must be GIT or SVN"); } } /** * Skip run. * * @return true, if successful */ protected boolean skipRun() { if (Boolean.valueOf(System.getProperty(PROFILE_SYNC_DISABLE))) { return true; } if (StringUtils.isBlank(this.changeSetURL)) { getLog().info("No SCM configuration will be done - no 'changeSetUrl' parameter was provided"); return true; } else { return false; } } //~--- get methods --------------------------------------------------------- /** * Gets the password. * * @return the password * @throws MojoExecutionException the mojo execution exception */ protected char[] getPassword() throws MojoExecutionException // protected String getPassword() throws MojoExecutionException { if (pwd == null) { pwd = System.getProperty(PROFILE_SYNC_PWD_PROPERTY) .toCharArray(); // still blank, try the passed in param if (pwd.length == 0) // if (StringUtils.isBlank(pwd)) { pwd = this.profileSyncPassword.toCharArray(); } // still no password, prompt if allowed if ((pwd.length == 0) &&!Boolean.valueOf(System.getProperty(PROFILE_SYNC_NO_PROMPTS))) { final Callable<Void> callable = () -> { try { if (!ProfilesMojoBase.this.disableHintGiven) { System.out.println( "To disable remote sync during build, add '-D" + PROFILE_SYNC_DISABLE + "=true' to your maven command"); ProfilesMojoBase.this.disableHintGiven = true; } System.out.println("Enter the " + ProfilesMojoBase.this.changeSetURLType + " password for the Profiles/Changset remote store: (" + ProfilesMojoBase.this.changeSetURL + "):"); // Use console if available, for password masking final Console console = System.console(); if (console != null) { pwd = console.readPassword(); } else { final BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); pwd = br.readLine() .toCharArray(); } } catch (final IOException e) { throw new MojoExecutionException( "Error reading password from console"); } return null; }; try { Executors.newSingleThreadExecutor(r -> { final Thread t = new Thread(r, "User Password Prompt Thread"); t.setDaemon(true); return t; }).submit(callable).get(2, TimeUnit.MINUTES); } catch (TimeoutException | InterruptedException e) { throw new MojoExecutionException("Password not provided within timeout"); } catch (final ExecutionException ee) { throw((ee.getCause() instanceof MojoExecutionException) ? (MojoExecutionException) ee.getCause() : new MojoExecutionException("Unexpected", ee.getCause())); } } } return pwd; } /** * Gets the profile sync impl. * * @return the profile sync impl * @throws MojoExecutionException the mojo execution exception */ protected SyncFiles getProfileSyncImpl() throws MojoExecutionException { if (this.changeSetURLType.equalsIgnoreCase("GIT")) { final SyncFiles svc = LookupService.getService(SyncFiles.class, "GIT"); if (svc == null) { throw new MojoExecutionException( "Unable to load the GIT implementation of the ProfileSyncI interface." + " Is sh.isaac.gui.modules.sync-git listed as a dependency for the mojo execution?"); } svc.setRootLocation(this.userProfileFolderLocation); return svc; } else if (this.changeSetURLType.equalsIgnoreCase("SVN")) { final SyncFiles svc = LookupService.getService(SyncFiles.class, "SVN"); if (svc == null) { throw new MojoExecutionException( "Unable to load the SVN implementation of the ProfileSyncI interface." + " Is sh.isaac.gui.modules.sync-svn listed as a dependency for the mojo execution?"); } svc.setRootLocation(this.userProfileFolderLocation); return svc; } else { throw new MojoExecutionException("Unsupported change set URL Type"); } } /** * Does the necessary substitution to put the contents of getUserName() into the URL, if a known pattern needing substitution is found. * ssh://someuser@csfe.aceworkspace.net:29418/... for example needs to become: * ssh://<getUsername()>@csfe.aceworkspace.net:29418/... * * @return the url * @throws MojoExecutionException the mojo execution exception */ protected String getURL() throws MojoExecutionException { return getProfileSyncImpl().substituteURL(this.changeSetURL, getUsername()); } /** * Gets the username. * * @return the username * @throws MojoExecutionException the mojo execution exception */ protected String getUsername() throws MojoExecutionException { if (username == null) { username = System.getProperty(PROFILE_SYNC_USERNAME_PROPERTY); // still blank, try property if (StringUtils.isBlank(username)) { username = this.profileSyncUsername; } // still no username, prompt if allowed if (StringUtils.isBlank(username) &&!Boolean.valueOf(System.getProperty(PROFILE_SYNC_NO_PROMPTS))) { final Callable<Void> callable = () -> { if (!ProfilesMojoBase.this.disableHintGiven) { System.out.println("To disable remote sync during build, add '-D" + PROFILE_SYNC_DISABLE + "=true' to your maven command"); ProfilesMojoBase.this.disableHintGiven = true; } try { System.out.println("Enter the " + ProfilesMojoBase.this.changeSetURLType + " username for the Profiles/Changset remote store (" + ProfilesMojoBase.this.changeSetURL + "):"); final BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); username = br.readLine(); } catch (final IOException e) { throw new MojoExecutionException( "Error reading username from console"); } return null; }; try { Executors.newSingleThreadExecutor(r -> { final Thread t = new Thread(r, "User Prompt Thread"); t.setDaemon(true); return t; }).submit(callable).get(2, TimeUnit.MINUTES); } catch (TimeoutException | InterruptedException e) { throw new MojoExecutionException("Username not provided within timeout"); } catch (final ExecutionException ee) { throw((ee.getCause() instanceof MojoExecutionException) ? (MojoExecutionException) ee.getCause() : new MojoExecutionException("Unexpected", ee.getCause())); } } } return username; } }