/** * Constructs a {@link DockerPlatform}. * * @param clientPlatform the client {@link Platform} * @param docker the {@link Docker} * @param options the {@link Option}s */ public DockerPlatform(Platform clientPlatform, Docker docker, Option... options) { this(clientPlatform.getName(), clientPlatform, docker, options); }
@Override public A launch(Platform platform, MetaClass<A> metaClass, OptionsByType optionsByType) { // establish the diagnostics output table Table diagnosticsTable = new Table(); diagnosticsTable.getOptions().add(Table.orderByColumn(0)); if (platform != null) { diagnosticsTable.addRow("Target Platform", platform.getName()); } // ----- establish the launch Options for the Application ----- // add the platform options OptionsByType launchOptions = OptionsByType.of(platform.getOptions()).addAll(optionsByType); // add the meta-class options metaClass.onLaunching(platform, launchOptions); // ---- establish the default Options ---- // define the PlatformSeparators as Unix if they are not already defined launchOptions.addIfAbsent(PlatformSeparators.forUnix()); // define the default Platform Shell (assume BASH) launchOptions.addIfAbsent(Shell.is(Shell.Type.BASH)); // define the "local.address" variable so that is can be used for resolving this platform // address launchOptions.add( Variable.with("local.address", LocalPlatform.get().getAddress().getHostAddress())); // ----- establish an identity for the application ----- // add a unique runtime id for expression support launchOptions.add(Variable.with("bedrock.runtime.id", UUID.randomUUID())); // ----- establish default Profiles for this Platform (and Builder) ----- // auto-detect and add externally defined profiles launchOptions.addAll(Profiles.getProfiles()); // ----- notify the Profiles that the application is about to be launched ----- for (Profile profile : launchOptions.getInstancesOf(Profile.class)) { profile.onLaunching(platform, metaClass, launchOptions); } // ----- add the diagnostic table to the options so it can be used by the terminal ----- launchOptions.add(diagnosticsTable); // ----- prior to launching the application, let the implementation enhance the launch options // ----- onLaunching(launchOptions); // ----- give the MetaClass a last chance to manipulate any options ----- metaClass.onLaunch(platform, launchOptions); // ----- determine the display name for the application ----- DisplayName displayName = getDisplayName(launchOptions); // determine the Executable Executable executable = launchOptions.get(Executable.class); // ----- deploy remote application artifacts ----- // determine the DeploymentArtifacts based on those specified by the Deployment option ArrayList<DeploymentArtifact> artifactsToDeploy = new ArrayList<>(); Deployment deployment = launchOptions.get(Deployment.class); if (deployment != null) { try { artifactsToDeploy.addAll(deployment.getDeploymentArtifacts(platform, launchOptions)); } catch (Exception e) { throw new RuntimeException("Failed to determine artifacts to deploy", e); } } // determine the separators for the platform PlatformSeparators separators = launchOptions.get(PlatformSeparators.class); // assume the remote directory is the working directory WorkingDirectory workingDirectory = launchOptions.getOrSetDefault( WorkingDirectory.class, WorkingDirectory.temporaryDirectory()); File remoteDirectoryFile = workingDirectory.resolve(platform, launchOptions); if (remoteDirectoryFile == null) { remoteDirectoryFile = WorkingDirectory.temporaryDirectory().resolve(platform, launchOptions); } String remoteDirectory = separators.asPlatformFileName(remoteDirectoryFile.toString()); // Set the resolved working directory back into the options launchOptions.add(WorkingDirectory.at(remoteDirectoryFile)); if (remoteDirectoryFile != null) { diagnosticsTable.addRow("Working Directory", remoteDirectoryFile.toString()); } // Obtain the RemoteShell that will be used to launch the process RemoteTerminalBuilder terminalBuilder = launchOptions.getOrSetDefault(RemoteTerminalBuilder.class, RemoteTerminals.ssh()); RemoteTerminal terminal = terminalBuilder.build(platform); // create the working directory terminal.makeDirectories(remoteDirectory, launchOptions); // deploy any artifacts required Deployer deployer = launchOptions.getOrSetDefault(Deployer.class, new SftpDeployer()); DeployedArtifacts deployedArtifacts = deployer.deploy(artifactsToDeploy, remoteDirectory, platform, launchOptions.asArray()); // add the remote directory as something to clean up deployedArtifacts.add(remoteDirectoryFile); if (!deployedArtifacts.isEmpty()) { // when we've deployed artifacts we need to add a listener to clean them up launchOptions.add( Decoration.of( new ApplicationListener<A>() { @Override public void onClosing(A application, OptionsByType optionsByType) { // nothing to do on closing } @Override public void onClosed(A application, OptionsByType optionsByType) { // undeploy the deployed artifacts deployer.undeploy(deployedArtifacts, platform, launchOptions.asArray()); } @Override public void onLaunched(A application) { // nothing to do after launching } })); } // Realize the application arguments Arguments arguments = launchOptions.get(Arguments.class); List<String> argList = arguments.resolve(platform, launchOptions); // Set the actual arguments used back into the options launchOptions.add(Arguments.of(argList)); // TODO: put a try/catch around the terminal.launch here so we can clean up the RemoteExecutor // if // the application failed to launch // determine the application class that will represent the running application Class<? extends A> applicationClass = metaClass.getImplementationClass(platform, launchOptions); diagnosticsTable.addRow("Application", displayName.resolve(launchOptions)); if (argList.size() > 0) { diagnosticsTable.addRow( "Application Arguments ", argList.stream().collect(Collectors.joining(" "))); } diagnosticsTable.addRow( "Application Launch Time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); // ----- start the process and establish the application ----- // launch the remote process RemoteApplicationProcess remoteProcess = terminal.launch(this, applicationClass, launchOptions); // adapt the remote process into something that the application can use ApplicationProcess process = adapt(remoteProcess); // create the Application based on the RemoteApplicationProcess A application; try { // attempt to find a constructor(Platform, JavaApplicationProcess, Options) Constructor<? extends A> constructor = ReflectionHelper.getCompatibleConstructor( applicationClass, platform.getClass(), process.getClass(), OptionsByType.class); // create the application application = constructor.newInstance(platform, process, launchOptions); } catch (Exception e) { throw new RuntimeException( "Failed to instantiate the Application class specified by the MetaClass:" + metaClass, e); } // ----- after launching the application, let the implementation interact with the application // ----- onLaunched(application, launchOptions); // ----- notify the MetaClass that the application has been launched ----- metaClass.onLaunched(platform, application, launchOptions); // ----- notify the Profiles that the application has been launched ----- for (Profile profile : launchOptions.getInstancesOf(Profile.class)) { profile.onLaunched(platform, application, launchOptions); } // ----- notify all of the application listeners ----- // notify the ApplicationListener-based Options that the application has been launched for (ApplicationListener listener : launchOptions.getInstancesOf(ApplicationListener.class)) { listener.onLaunched(application); } return application; }