From 2dd432a46ef234c790cf0a3fb7e1f19f66238a53 Mon Sep 17 00:00:00 2001 From: nsingh Date: Thu, 5 Jan 2017 10:58:56 -0800 Subject: [PATCH 1/5] Initial commit of CF vscode extension --- .../commons/commons-cf/.classpath | 26 + vscode-extensions/commons/commons-cf/.project | 29 + .../.settings/org.eclipse.jdt.core.prefs | 5 + .../.settings/org.eclipse.m2e.core.prefs | 4 + vscode-extensions/commons/commons-cf/pom.xml | 49 + .../cloudfoundry/client/CFAppState.java | 19 + .../cloudfoundry/client/CFApplication.java | 35 + .../client/CFApplicationArchive.java | 15 + .../client/CFApplicationDetail.java | 17 + .../cloudfoundry/client/CFBuildpack.java | 15 + .../cloudfoundry/client/CFCloudDomain.java | 15 + .../commons/cloudfoundry/client/CFEntity.java | 15 + .../cloudfoundry/client/CFExceptions.java | 27 + .../cloudfoundry/client/CFInstanceState.java | 15 + .../cloudfoundry/client/CFInstanceStats.java | 17 + .../cloudfoundry/client/CFOrganization.java | 17 + .../client/CFServiceInstance.java | 24 + .../commons/cloudfoundry/client/CFSpace.java | 18 + .../commons/cloudfoundry/client/CFStack.java | 15 + .../cloudfoundry/client/HealthChecks.java | 19 + .../cloudfoundry/client/InstanceState.java | 30 + .../cloudfoundry/client/LoginMethod.java | 27 + .../client/RequestErrorHandler.java | 25 + .../cloudfoundry/client/SshClientSupport.java | 27 + .../commons/cloudfoundry/client/SshHost.java | 53 + .../client/params/CFClientParams.java | 151 ++ .../client/params/CFClientParamsFactory.java | 55 + .../client/params/CFCredentials.java | 132 ++ .../client/params/CfCliParamsProvider.java | 113 ++ .../client/v2/ApplicationExtras.java | 32 + .../client/v2/CFApplicationDetailData.java | 62 + .../client/v2/CFApplicationSummaryData.java | 130 ++ .../client/v2/CFPushArguments.java | 157 ++ .../cloudfoundry/client/v2/CFRoute.java | 73 + .../cloudfoundry/client/v2/CFWrappingV2.java | 287 ++++ .../client/v2/CancelationTokens.java | 94 ++ .../client/v2/ClientRequests.java | 85 ++ .../client/v2/CloudFoundryClientCache.java | 250 ++++ .../client/v2/DefaultClientRequestsV2.java | 1330 +++++++++++++++++ .../DefaultCloudFoundryClientFactoryV2.java | 29 + .../client/v2/DeploymentProperties.java | 53 + .../cloudfoundry/client/v2/ReactorUtils.java | 293 ++++ .../src/test/java/CFClientTest.java | 40 + .../vscode/commons/util/ExceptionUtil.java | 4 + vscode-extensions/commons/pom.xml | 7 +- 45 files changed, 3934 insertions(+), 1 deletion(-) create mode 100644 vscode-extensions/commons/commons-cf/.classpath create mode 100644 vscode-extensions/commons/commons-cf/.project create mode 100644 vscode-extensions/commons/commons-cf/.settings/org.eclipse.jdt.core.prefs create mode 100644 vscode-extensions/commons/commons-cf/.settings/org.eclipse.m2e.core.prefs create mode 100644 vscode-extensions/commons/commons-cf/pom.xml create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFAppState.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplication.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplicationArchive.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplicationDetail.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFBuildpack.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFCloudDomain.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFEntity.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFExceptions.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFInstanceState.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFInstanceStats.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFOrganization.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFServiceInstance.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFSpace.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFStack.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/HealthChecks.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/InstanceState.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/LoginMethod.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/RequestErrorHandler.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/SshClientSupport.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/SshHost.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParams.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParamsFactory.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFCredentials.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CfCliParamsProvider.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ApplicationExtras.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFApplicationDetailData.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFApplicationSummaryData.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFPushArguments.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFRoute.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFWrappingV2.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CancelationTokens.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ClientRequests.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DeploymentProperties.java create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ReactorUtils.java create mode 100644 vscode-extensions/commons/commons-cf/src/test/java/CFClientTest.java diff --git a/vscode-extensions/commons/commons-cf/.classpath b/vscode-extensions/commons/commons-cf/.classpath new file mode 100644 index 000000000..af1430be1 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/.classpath @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vscode-extensions/commons/commons-cf/.project b/vscode-extensions/commons/commons-cf/.project new file mode 100644 index 000000000..35cfeeec6 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/.project @@ -0,0 +1,29 @@ + + + commons-cf + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/vscode-extensions/commons/commons-cf/.settings/org.eclipse.jdt.core.prefs b/vscode-extensions/commons/commons-cf/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..714351aec --- /dev/null +++ b/vscode-extensions/commons/commons-cf/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/vscode-extensions/commons/commons-cf/.settings/org.eclipse.m2e.core.prefs b/vscode-extensions/commons/commons-cf/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 000000000..f897a7f1c --- /dev/null +++ b/vscode-extensions/commons/commons-cf/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/vscode-extensions/commons/commons-cf/pom.xml b/vscode-extensions/commons/commons-cf/pom.xml new file mode 100644 index 000000000..677e43a09 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/pom.xml @@ -0,0 +1,49 @@ + + 4.0.0 + commons-cf + commons-cf + Common code related to 'accessing Cloud Foundry' + + + org.springframework.ide.vscode + commons-parent + 0.0.1-SNAPSHOT + ../pom.xml + + + + 2.1.0.RELEASE + 3.0.4.RELEASE + 0.6.0.RELEASE + + + + + + org.springframework.ide.vscode + commons-util + ${project.version} + + + org.cloudfoundry + cloudfoundry-client-reactor + ${cloudfoundry.client.version} + + + org.cloudfoundry + cloudfoundry-operations + ${cloudfoundry.client.version} + + + io.projectreactor + reactor-core + ${client.reactor.version} + + + io.projectreactor.ipc + reactor-netty + ${reactor.netty} + + + \ No newline at end of file diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFAppState.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFAppState.java new file mode 100644 index 000000000..287f5772c --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFAppState.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public enum CFAppState { + + STOPPED, + STARTED, + UNKNOWN + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplication.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplication.java new file mode 100644 index 000000000..bc051e004 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplication.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public interface CFApplication extends CFEntity { + //TODO: lots of this infos should be moved to application details + + int getInstances(); + int getRunningInstances(); + int getMemory(); + UUID getGuid(); + List getServices(); + String getBuildpackUrl(); + List getUris(); + CFAppState getState(); + int getDiskQuota(); + Integer getTimeout(); + String getCommand(); + String getStack(); + + Map getEnvAsMap(); + String getHealthCheckType(); +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplicationArchive.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplicationArchive.java new file mode 100644 index 000000000..52cd37157 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplicationArchive.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public interface CFApplicationArchive { + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplicationDetail.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplicationDetail.java new file mode 100644 index 000000000..c36457734 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFApplicationDetail.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +import java.util.List; + +public interface CFApplicationDetail extends CFApplication { + List getInstanceDetails(); +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFBuildpack.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFBuildpack.java new file mode 100644 index 000000000..abcffc0cd --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFBuildpack.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public interface CFBuildpack { + String getName(); +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFCloudDomain.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFCloudDomain.java new file mode 100644 index 000000000..324a0c838 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFCloudDomain.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public interface CFCloudDomain { + String getName(); +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFEntity.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFEntity.java new file mode 100644 index 000000000..84d5ef5cf --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFEntity.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public interface CFEntity { + String getName(); +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFExceptions.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFExceptions.java new file mode 100644 index 000000000..0e5b228ee --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFExceptions.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +import org.springframework.ide.vscode.commons.util.ExceptionUtil; + +/** + * Static methods to recognize specific types of exceptions CF client + * may throw. + * + * @author Kris De Volder + */ +public class CFExceptions { + + public static boolean isSSLCertificateFailure(Exception e) { + Throwable cause = ExceptionUtil.getDeepestCause(e); + return cause.getClass().getName().equals("sun.security.provider.certpath.SunCertPathBuilderException"); + } +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFInstanceState.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFInstanceState.java new file mode 100644 index 000000000..57feb7ea3 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFInstanceState.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public enum CFInstanceState { + RUNNING, CRASHED, FLAPPING, STARTING, DOWN, UNKNOWN +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFInstanceStats.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFInstanceStats.java new file mode 100644 index 000000000..1ba1eb539 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFInstanceStats.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public interface CFInstanceStats { + + CFInstanceState getState(); + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFOrganization.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFOrganization.java new file mode 100644 index 000000000..d354e0d3c --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFOrganization.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +import java.util.UUID; + +public interface CFOrganization extends CFEntity { + UUID getGuid(); +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFServiceInstance.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFServiceInstance.java new file mode 100644 index 000000000..9f549e870 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFServiceInstance.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public interface CFServiceInstance extends CFEntity { + + String getName(); + String getService(); + String getPlan(); + String getDescription(); + String getDocumentationUrl(); + String getDashboardUrl(); + + //TODO: last operation info? + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFSpace.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFSpace.java new file mode 100644 index 000000000..0568b0e55 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFSpace.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +import java.util.UUID; + +public interface CFSpace extends CFEntity { + CFOrganization getOrganization(); + UUID getGuid(); +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFStack.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFStack.java new file mode 100644 index 000000000..8cb8b34d2 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFStack.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public interface CFStack extends CFEntity { + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/HealthChecks.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/HealthChecks.java new file mode 100644 index 000000000..96eeef68b --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/HealthChecks.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public class HealthChecks { + + public static final String HC_NONE = "none"; + public static final String HC_PORT = "port"; + public static final String[] HC_ALL = {HC_NONE, HC_PORT}; + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/InstanceState.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/InstanceState.java new file mode 100644 index 000000000..a1705b2bb --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/InstanceState.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2009-2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +/** + * Enum used for the state of an instance + * + * Note: copied over from CF V1 client code. + * + * @author Thomas Risberg + */ +public enum InstanceState { + DOWN, STARTING, RUNNING, CRASHED, FLAPPING, UNKNOWN; + + public static InstanceState valueOfWithDefault(String s) { + try { + return InstanceState.valueOf(s); + } catch (IllegalArgumentException e) { + return InstanceState.UNKNOWN; + } + } +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/LoginMethod.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/LoginMethod.java new file mode 100644 index 000000000..43e038d1a --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/LoginMethod.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; +public enum LoginMethod { + PASSWORD, + TEMPORARY_CODE; + + public String getLabel() { + String[] pieces = name().split("_"); + StringBuilder label = new StringBuilder(); + for (int i = 0; i < pieces.length; i++) { + if (i>0) { + label.append(" "); + } + label.append(pieces[i].toLowerCase()); + } + return label.toString(); + } +} \ No newline at end of file diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/RequestErrorHandler.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/RequestErrorHandler.java new file mode 100644 index 000000000..6b10525fc --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/RequestErrorHandler.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +public class RequestErrorHandler { + + /** + * + * @param e + * @return true if request error should be treated as an error and thrown. False error + * should be ignored. + */ + public boolean throwError(Throwable e) { + return true; + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/SshClientSupport.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/SshClientSupport.java new file mode 100644 index 000000000..45ac00bea --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/SshClientSupport.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +import java.util.UUID; + +public interface SshClientSupport { + + SshHost getSshHost() throws Exception; + String getSshUser(String appName, int instance) throws Exception; + String getSshCode() throws Exception; + + /** + * Deprecated because it is not supported with V2. Use the method based on appName instead. + */ + @Deprecated + String getSshUser(UUID appGuid, int instance) throws Exception; + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/SshHost.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/SshHost.java new file mode 100644 index 000000000..d6103c924 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/SshHost.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2015, 2016 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + +/** + * Info object containing various bits of info about the host to which an ssh + * client may wish to connect. + * + * @author Kris De Volder + */ +public class SshHost { + + final private String host; + + final private int port; + + final private String fingerPrint; + + public SshHost(String host, int port, String fingerPrint) { + super(); + this.host = host; + this.port = port; + this.fingerPrint = fingerPrint; + } + + public String getFingerPrint() { + return fingerPrint; + } + + public int getPort() { + return port; + } + + public String getHost() { + return host; + } + + @Override + public String toString() { + return "SshHost [host=" //$NON-NLS-1$ + + host + ", port=" //$NON-NLS-1$ + + port + ", fingerPrint=" //$NON-NLS-1$ + + fingerPrint + "]";//$NON-NLS-1$ + } +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParams.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParams.java new file mode 100644 index 000000000..f17674c08 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParams.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.params; + +import java.net.URI; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFCredentials.CFCredentialType; +import org.springframework.ide.vscode.commons.util.Assert; + +/** + * All the parameters needed to create a CF client. + * + * @author Kris De Volder + */ +public class CFClientParams { + + private static final Logger logger = Logger.getLogger(CFClientParams.class.getName()); + + private final String apiUrl; + private final String username; + private CFCredentials credentials; + private final boolean skipSslValidation; + + private String orgName; // optional + private String spaceName; //optional + + public CFClientParams(String apiUrl, + String username, + CFCredentials credentials, + String orgName, + String spaceName, + boolean skipSslValidation + ) { + Assert.isNotNull(apiUrl); + Assert.isNotNull(credentials); + if (credentials.getType()==CFCredentialType.PASSWORD) { + Assert.isNotNull(username); + } + this.apiUrl = apiUrl; + this.username = username; + this.credentials = credentials; + this.skipSslValidation = skipSslValidation; + this.orgName = orgName; + this.spaceName = spaceName; + } + + public CFCredentials getCredentials() { + return credentials; + } + + public String getUsername() { + return username; + } + + public boolean skipSslValidation() { + return skipSslValidation; + } + + + + public String getApiUrl() { + return apiUrl; + } + + public String getOrgName() { + return orgName; + } + + public void setOrgName(String orgName) { + this.orgName = orgName; + } + + public String getSpaceName() { + return spaceName; + } + + public void setSpaceName(String spaceName) { + this.spaceName = spaceName; + } + + public String getHost() { + try { + URI uri = new URI(getApiUrl()); + return uri.getHost(); + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + + } + return null; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((apiUrl == null) ? 0 : apiUrl.hashCode()); + result = prime * result + ((orgName == null) ? 0 : orgName.hashCode()); + result = prime * result + ((credentials == null) ? 0 : credentials.hashCode()); + result = prime * result + ((spaceName == null) ? 0 : spaceName.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CFClientParams other = (CFClientParams) obj; + if (apiUrl == null) { + if (other.apiUrl != null) + return false; + } else if (!apiUrl.equals(other.apiUrl)) + return false; + if (orgName == null) { + if (other.orgName != null) + return false; + } else if (!orgName.equals(other.orgName)) + return false; + if (credentials == null) { + if (other.credentials != null) + return false; + } else if (!credentials.equals(other.credentials)) + return false; + if (spaceName == null) { + if (other.spaceName != null) + return false; + } else if (!spaceName.equals(other.spaceName)) + return false; + if (username == null) { + if (other.username != null) + return false; + } else if (!username.equals(other.username)) + return false; + return true; + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParamsFactory.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParamsFactory.java new file mode 100644 index 000000000..b48092c08 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParamsFactory.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.params; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Provider; + +public class CFClientParamsFactory { + + public static final CFClientParamsFactory INSTANCE = new CFClientParamsFactory(); + + @SuppressWarnings("unchecked") + private List>> providers = new ArrayList<>(); + + private CFClientParamsFactory() { + // Singleton + addProvider(new CfCliParamsProvider()); + } + + /** + * Adds provider to the front of the registered providers such that it would + * be called first when a request is made for CF client params + * + * @param provider + */ + public void addProvider(Provider> provider) { + if (provider != null) { + providers.add(provider); + } + } + + public List getParams() { + + // Start from the last provider, which is the highest priority + for (int i = providers.size() - 1; i >= 0; i--) { + List params = providers.get(i).get(); + if (params != null && !params.isEmpty()) { + return params; + } + } + return null; + + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFCredentials.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFCredentials.java new file mode 100644 index 000000000..670c1280d --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFCredentials.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.params; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.LoginMethod; +import org.springframework.ide.vscode.commons.util.Assert; + +public class CFCredentials { + + public enum CFCredentialType { + PASSWORD, + TEMPORARY_CODE, + REFRESH_TOKEN; + + public LoginMethod toLoginMethod() { + switch (this) { + case PASSWORD: + return LoginMethod.PASSWORD; + case TEMPORARY_CODE: + return LoginMethod.TEMPORARY_CODE; + default: + return null; + } + } + } + + private final CFCredentialType type; + private final String secret; + + /** + * Deprecated, use fromLogin instead + */ + @Deprecated + public static CFCredentials fromPassword(String password) { + return fromLogin(LoginMethod.PASSWORD, password); + } + + + public static CFCredentials fromLogin(LoginMethod method, String secret) { + CFCredentialType type; + switch (method) { + case PASSWORD: + type = CFCredentialType.PASSWORD; + break; + case TEMPORARY_CODE: + type = CFCredentialType.TEMPORARY_CODE; + break; + default: + throw new IllegalStateException("Bug! Missing switch case?"); + } + return new CFCredentials(type, secret); + } + + public static CFCredentials fromRefreshToken(String refreshToken) { + Assert.isNotNull(refreshToken); + return new CFCredentials(CFCredentialType.REFRESH_TOKEN, refreshToken); + } + + public String getSecret() { + return secret; + } + + ///////////////////////////////////////////////////////////////////////// + + + /** + * Private constuctor, use static `fromXXX` factory methods instead. + */ + private CFCredentials(CFCredentialType type, String secret) { + this.type = type; + this.secret = secret; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((secret == null) ? 0 : secret.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CFCredentials other = (CFCredentials) obj; + if (secret == null) { + if (other.secret != null) + return false; + } else if (!secret.equals(other.secret)) + return false; + if (type != other.type) + return false; + return true; + } + + @Override + public String toString() { + return "CFCredentials [type=" + type + ", secret=" + hidePassword(type, secret) + "]"; + } + + private String hidePassword(CFCredentialType type, String password) { + if (password==null) { + return null; + } + return type==CFCredentialType.PASSWORD + ? "****" + : password; + } + + + public CFCredentialType getType() { + return type; + } + + public static CFCredentials fromSsoToken(String ssoToken) { + return CFCredentials.fromLogin(LoginMethod.TEMPORARY_CODE, ssoToken); + } +} \ No newline at end of file diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CfCliParamsProvider.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CfCliParamsProvider.java new file mode 100644 index 000000000..0c8335cc5 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CfCliParamsProvider.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.params; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.inject.Provider; + +import org.springframework.ide.vscode.commons.util.ExternalCommand; +import org.springframework.ide.vscode.commons.util.ExternalProcess; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Provides existing Cloud Foundry client params, like target and credentials, + * from the CLI config.json in the file system. + * + * + */ +public class CfCliParamsProvider implements Provider> { + + public static final String TARGET = "Target"; + public static final String REFRESH_TOKEN = "RefreshToken"; + public static final String ORGANIZATION_FIELDS = "OrganizationFields"; + public static final String SPACE_FIELDS = "SpaceFields"; + public static final String NAME = "Name"; + public static final String SSL_DISABLED = "SSLDisabled"; + + private static Logger logger = Logger.getLogger(CfCliParamsProvider.class.getName()); + + @Override + public List get() { + try { + File file = getConfigJsonFile(); + if (file != null) { + ObjectMapper mapper = new ObjectMapper(); + Map userData = mapper.readValue(file, Map.class); + if (userData != null) { + String refreshToken = (String) userData.get(REFRESH_TOKEN); + if (refreshToken == null) { + return null; + } + CFCredentials credentials = CFCredentials.fromRefreshToken(refreshToken); + boolean sslDisabled = (Boolean) userData.get(SSL_DISABLED); + String target = (String) userData.get(TARGET); + Map orgFields = (Map) userData.get(ORGANIZATION_FIELDS); + Map spaceFields = (Map) userData.get(SPACE_FIELDS); + if (target != null && orgFields != null && spaceFields != null) { + String orgName = (String) orgFields.get(NAME); + String spaceName = (String) spaceFields.get(NAME); + List params = new ArrayList<>(); + params.add(new CFClientParams(target, null, credentials, orgName, spaceName, sslDisabled)); + return params; + } + } + } + } catch (IOException | InterruptedException e) { + log(e); + } + + return null; + } + + private File getConfigJsonFile() throws IOException, InterruptedException { + if (!System.getProperty("os.name").toLowerCase().startsWith("win")) { + String homeDir = getUnixHomeDir(); + if (homeDir != null) { + if (!homeDir.endsWith("/")) { + homeDir += '/'; + } + String filePath = homeDir + ".cf/config.json"; + File file = new File(filePath); + if (file.exists() && file.canRead()) { + return file; + } + } + } + return null; + } + + private String getUnixHomeDir() throws IOException, InterruptedException { + File currentWorkDir = null; /* + * use current working dir which means pass + * null + */ + ExternalProcess homeDirProcess = new ExternalProcess(currentWorkDir, + new ExternalCommand("/bin/bash", "-c", "echo $HOME"), true); + String homeDir = homeDirProcess.getOut(); + if (homeDir != null) { + homeDir = homeDir.trim(); // remove any new line chars + } + return homeDir; + } + + private void log(Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ApplicationExtras.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ApplicationExtras.java new file mode 100644 index 000000000..4235a2ffe --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ApplicationExtras.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import java.util.List; +import java.util.Map; + +import reactor.core.publisher.Mono; + +/** + * Extrat bits of info that are 'prefetched' asynchronously to fill + * out the CFApplication with info that C2 client doesn't initially return. + * + * @author Kris De Volder + */ +public interface ApplicationExtras { + Mono> getEnv(); + Mono> getServices(); + Mono getBuildpack(); + Mono getStack(); + Mono getTimeout(); + Mono getCommand(); + Mono getHealthCheckType(); +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFApplicationDetailData.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFApplicationDetailData.java new file mode 100644 index 000000000..d7d2362ca --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFApplicationDetailData.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import java.util.List; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFApplicationDetail; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFInstanceStats; + +public class CFApplicationDetailData extends CFApplicationSummaryData implements CFApplicationDetail { + + private List instanceDetails; + + public CFApplicationDetailData( + CFApplicationSummaryData app, + List instanceDetails + ) { + super( + app.getName(), + app.getInstances(), + app.getRunningInstances(), + app.getMemory(), + app.getGuid(), + app.getUris(), + app.getState(), + app.getDiskQuota(), + app.extras + ); + this.instanceDetails = instanceDetails; + } + + @Override + public List getInstanceDetails() { + return instanceDetails; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("CFApplicationDetail(\n"); + buf.append(" name = "+getName()+"\n"); + buf.append(" instance = "+getInstances()+"\n"); + buf.append(" runningInstances = "+getRunningInstances()+"\n"); + buf.append(" memory = "+getMemory()+"\n"); + buf.append(" guid = "+getGuid()+"\n"); + buf.append(" uris = "+getUris()+"\n"); + buf.append(" state = "+getState()+"\n"); + buf.append(" diskQuota = "+getDiskQuota()+"\n"); + buf.append(" instanceDetails = "+getInstanceDetails()+"\n"); + buf.append(")\n"); + return buf.toString(); + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFApplicationSummaryData.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFApplicationSummaryData.java new file mode 100644 index 000000000..acb99c818 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFApplicationSummaryData.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFAppState; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFApplication; + +public class CFApplicationSummaryData implements CFApplication { + + private String name; + private int instances; + private int runningInstances; + private int memory; + private UUID guid; + private List uris; + private CFAppState state; + private int diskQuota; + protected ApplicationExtras extras; + + public CFApplicationSummaryData( + String name, + int instances, + int runningInstances, + int memory, + UUID guid, + List uris, + CFAppState state, + int diskQuota, + ApplicationExtras extras + ) { + super(); + this.name = name; + this.instances = instances; + this.runningInstances = runningInstances; + this.memory = memory; + this.guid = guid; + this.uris = uris; + this.state = state; + this.diskQuota = diskQuota; + this.extras = extras; + } + + @Override + public String getName() { + return name; + } + + @Override + public int getInstances() { + return instances; + } + + @Override + public int getRunningInstances() { + return runningInstances; + } + + @Override + public int getMemory() { + return memory; + } + + @Override + public UUID getGuid() { + return guid; + } + + @Override + public List getServices() { + return extras.getServices().block(); + } + + @Override + public String getBuildpackUrl() { + return extras.getBuildpack().block(); + } + + @Override + public List getUris() { + return uris; + } + + @Override + public CFAppState getState() { + return state; + } + + @Override + public int getDiskQuota() { + return diskQuota; + } + + @Override + public Integer getTimeout() { + return extras.getTimeout().block(); + } + + @Override + public String getHealthCheckType() { + return extras.getHealthCheckType().block(); + } + + @Override + public String getCommand() { + return extras.getCommand().block(); + } + + @Override + public String getStack() { + return extras.getStack().block(); + } + + @Override + public Map getEnvAsMap() { + return extras.getEnv().block(); + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFPushArguments.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFPushArguments.java new file mode 100644 index 000000000..cd55e6f9e --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFPushArguments.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipFile; + +import org.springframework.ide.vscode.commons.util.ExceptionUtil; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Arguments passed to push operation. + * + * @author Kris De Volder + * @author Nieraj Singh + */ +public class CFPushArguments implements AutoCloseable { + private List routes = ImmutableList.of(); + private String appName; + private Integer memory; + private Integer diskQuota; + private Integer timeout; + private String buildpack; + private String command; + private String stack; + private String healthCheckType; + private Map env = ImmutableMap.of(); + private Integer instances; + private List services = ImmutableList.of(); + private ZipFile applicationData; + private boolean noStart = false; + + public CFPushArguments() { + } + + public String getAppName() { + return appName; + } + public void setAppName(String appName) { + this.appName = appName; + } + public Integer getMemory() { + return memory; + } + public void setMemory(Integer memory) { + this.memory = memory; + } + public Integer getDiskQuota() { + return diskQuota; + } + public void setDiskQuota(Integer diskQuota) { + this.diskQuota = diskQuota; + } + public Integer getTimeout() { + return timeout; + } + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + public String getBuildpack() { + return buildpack; + } + public void setBuildpack(String buildpack) { + this.buildpack = buildpack; + } + public String getCommand() { + return command; + } + public void setCommand(String command) { + this.command = command; + } + public String getStack() { + return stack; + } + public void setStack(String stack) { + this.stack = stack; + } + public Map getEnv() { + return env; + } + public void setEnv(Map env) { + this.env = env; + } + public Integer getInstances() { + return instances; + } + public void setInstances(Integer instances) { + this.instances = instances; + } + public List getServices() { + return services; + } + public void setServices(List services) { + this.services = services; + } + public ZipFile getApplicationData() { + return applicationData; + } + @Override + public void close() throws Exception { + if (applicationData!=null) { + applicationData.close(); + } + } + public void setApplicationData(File archive) throws Exception { + if (applicationData==null) { + ExceptionUtil.exception("Can only set this once", null); + } + this.applicationData = new ZipFile(archive); + } + public boolean isNoStart() { + return noStart; + } + public void setNoStart(boolean noStart) { + this.noStart = noStart; + } + public String getHealthCheckType() { + if (healthCheckType==null) { + return DeploymentProperties.DEFAULT_HEALTH_CHECK_TYPE; + } + return healthCheckType; + } + public void setHealthCheckType(String healthCheckType) { + this.healthCheckType = healthCheckType; + } + public List getRoutes() { + return routes; + } + public void setRoutes(Collection routes) { + this.routes = routes == null ? ImmutableList.of() : ImmutableList.copyOf(routes); + } + public void setRoutes(String... routes) { + setRoutes(ImmutableList.copyOf(routes)); + } + @Override + public String toString() { + return "CFPushArguments [appName=" + appName + ", routes=" + routes + ", memory=" + memory + ", diskQuota=" + + diskQuota + ", timeout=" + timeout + ", buildpack=" + buildpack + ", command=" + command + ", stack=" + + stack + ", env=" + env + ", instances=" + instances + ", services=" + services + ", noStart=" + + noStart + ", healthCheckType="+ healthCheckType+" ]"; + } + + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFRoute.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFRoute.java new file mode 100644 index 000000000..d6700dc24 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFRoute.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import org.cloudfoundry.operations.routes.Route; + +public class CFRoute { + + final private String domain; + final private String host; + final private String path; + + public CFRoute(Route route) { + this.domain = route.getDomain(); + this.host = route.getHost(); + this.path = route.getPath(); + } + + public CFRoute(String domain, String host, String path) { + super(); + this.domain = domain; + this.host = host; + this.path = path; + } + + public String getDomain() { + return domain; + } + + public String getHost() { + return host; + } + + public String getPath() { + return path; + } + + @Override + public String toString() { + return "CFRoute [domain=" + domain + ", host=" + host + ", path=" + path + "]"; + } + + public static CFRoute.Builder builder() { + return new Builder(); + } + public static class Builder { + private String domain; + private String host; + private String path = ""; + + public CFRoute build() { + return new CFRoute(this.domain, this.host, this.path); + } + + public Builder domain(String domain) { + this.domain = domain; + return this; + } + + public Builder host(String host) { + this.host = host; + return this; + } + } +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFWrappingV2.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFWrappingV2.java new file mode 100644 index 000000000..3e3c3a29c --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CFWrappingV2.java @@ -0,0 +1,287 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +import org.cloudfoundry.client.v2.buildpacks.BuildpackResource; +import org.cloudfoundry.client.v2.domains.DomainResource; +import org.cloudfoundry.operations.applications.ApplicationDetail; +import org.cloudfoundry.operations.applications.ApplicationSummary; +import org.cloudfoundry.operations.applications.InstanceDetail; +import org.cloudfoundry.operations.organizations.OrganizationSummary; +import org.cloudfoundry.operations.services.ServiceInstance; +import org.cloudfoundry.operations.services.ServiceInstanceType; +import org.cloudfoundry.operations.spaces.SpaceSummary; +import org.cloudfoundry.operations.stacks.Stack; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFAppState; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFApplication; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFApplicationDetail; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFCloudDomain; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFInstanceState; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFInstanceStats; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFOrganization; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFSpace; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack; + +import com.google.common.collect.ImmutableList; + +/** + * Various helper methods to 'wrap' objects returned by CF client into + * our own types, so that we do not directly expose library types to our + * code. + * + * @author Kris De Volder + */ +public class CFWrappingV2 { + + + private static final Logger logger = Logger.getLogger(DefaultClientRequestsV2.class.getName()); + + + public static CFBuildpack wrap(BuildpackResource rsrc) { + String name = rsrc.getEntity().getName(); + return new CFBuildpack() { + @Override + public String getName() { + return name; + } + }; + } + + public static CFApplicationDetail wrap(ApplicationDetail details, ApplicationExtras extras) { + if (details!=null) { + List instances = ImmutableList.copyOf( + details.getInstanceDetails() + .stream() + .map(CFWrappingV2::wrap) + .collect(Collectors.toList()) + ); + CFApplicationSummaryData summary = wrapSummary(details, extras); + return new CFApplicationDetailData( + summary, + instances + ); + } + return null; + } + + public static CFStack wrap(Stack stack) { + if (stack!=null) { + String name = stack.getName(); + return new CFStack() { + @Override + public String getName() { + return name; + } + + @Override + public String toString() { + return "CFStack("+name+")"; + } + }; + } + return null; + } + + public static CFApplicationDetail wrap( + CFApplicationSummaryData summary, + ApplicationDetail details + ) { + List instanceDetails = ImmutableList.copyOf( + details + .getInstanceDetails() + .stream() + .map(CFWrappingV2::wrap) + .collect(Collectors.toList()) + ); + return new CFApplicationDetailData(summary, instanceDetails); + } + + public static CFCloudDomain wrap(DomainResource domainRsrc) { + if (domainRsrc!=null) { + String name = domainRsrc.getEntity().getName(); + return new CFCloudDomain() { + public String getName() { + return name; + } + @Override + public String toString() { + return "CFCloudDomain("+name+")"; + } + }; + } + return null; + } + + public static CFInstanceStats wrap(InstanceDetail instanceDetail) { + return new CFInstanceStats() { + @Override + public CFInstanceState getState() { + try { + return CFInstanceState.valueOf(instanceDetail.getState()); + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + return CFInstanceState.UNKNOWN; + } + } + + @Override + public String toString() { + return ""+getState(); + } + }; + } + + private static CFApplicationSummaryData wrapSummary(ApplicationDetail app, ApplicationExtras extras) { + CFAppState state; + try { + state = CFAppState.valueOf(app.getRequestedState()); + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + state = CFAppState.UNKNOWN; + } + + return new CFApplicationSummaryData( + app.getName(), + app.getInstances(), + app.getRunningInstances(), + app.getMemoryLimit(), + UUID.fromString(app.getId()), + app.getUrls(), + state, + app.getDiskQuota(), + extras + ); + } + + public static CFApplication wrap(ApplicationSummary app, ApplicationExtras extras) { + CFAppState state; + try { + state = CFAppState.valueOf(app.getRequestedState()); + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + state = CFAppState.UNKNOWN; + } + + return new CFApplicationSummaryData( + app.getName(), + app.getInstances(), + app.getRunningInstances(), + app.getMemoryLimit(), + UUID.fromString(app.getId()), + app.getUrls(), + state, + app.getDiskQuota(), + extras + ); + } + + public static CFServiceInstance wrap(final ServiceInstance service) { + return new CFServiceInstance() { + + @Override + public String getName() { + return service.getName(); + } + + @Override + public String getPlan() { + return service.getPlan(); + } + + @Override + public String getDashboardUrl() { + return service.getDashboardUrl(); + } + + @Override + public String getService() { + if (service.getType()==ServiceInstanceType.USER_PROVIDED) { + return "user-provided"; + } else { + return service.getService(); + } + } + + @Override + public String getDescription() { + return service.getDescription(); + } + + @Override + public String getDocumentationUrl() { + return service.getDocumentationUrl(); + } + }; + } + + public static CFAppState wrapAppState(String s) { + try { + return CFAppState.valueOf(s); + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + return CFAppState.UNKNOWN; + } + } + + public static CFSpace wrap(OrganizationSummary org, SpaceSummary space) { + return new CFSpace() { + @Override + public String getName() { + return space.getName(); + } + @Override + public CFOrganization getOrganization() { + return wrap(org); + } + @Override + public UUID getGuid() { + return UUID.fromString(space.getId()); + } + + @Override + public String toString() { + return "CFSpace("+org.getName()+" / "+getName()+")"; + } + }; + } + + public static CFOrganization wrap(OrganizationSummary org) { + return new CFOrganization() { + @Override + public String getName() { + return org.getName(); + } + + @Override + public UUID getGuid() { + return UUID.fromString(org.getId()); + } + }; + } + + public static CFBuildpack buildpack(String name) { + return new CFBuildpack() { + @Override + public String getName() { + return name; + } + }; + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CancelationTokens.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CancelationTokens.java new file mode 100644 index 000000000..a3050d1e8 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CancelationTokens.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +/** + * Manages a set of CancelationTokens. + * + * @author Kris De Volder + */ +public class CancelationTokens { + + private String DEBUG = null; + + //Note: we don't actually have to keep a set of tokens explicitly. + // The tokens use a 'id' which is incremented on each new token. + //So it is easy to cancel all existing tokens based on a their + //id simply by remembering the 'id' where the cancelation + //occurred. All ids 'older' than the current id are 'canceled'. + + /** + * An uncancelable token that can be used by operations that don't + * need cancelation support. + */ + public static final CancelationToken NULL = new CancelationToken() { + @Override + public boolean isCanceled() { + return false; + } + }; + + private final Object SYNC = CancelationTokens.this; + + private int canceledAllBefore = 0; + private int nextId = 0; + + public CancelationTokens() { + } + + public CancelationTokens(String debug) { + this.DEBUG = debug; + } + + public interface CancelationToken { + boolean isCanceled(); + } + + public synchronized CancelationToken create() { + CancelationToken token = new ManagedToken(); + debug("creating cancelation token: "+token); + return token; + } + + private class ManagedToken implements CancelationToken { + private int id; + + private ManagedToken() { + synchronized (SYNC) { + this.id = nextId++; + } + } + + public boolean isCanceled() { + synchronized (SYNC) { + boolean isCanceled = id < canceledAllBefore; + debug("isCanceled? ["+id+"] => "+isCanceled); + return isCanceled; + } + } + + @Override + public String toString() { + return "CancelToken("+id+")"; + } + } + + public synchronized void cancelAll() { + canceledAllBefore = nextId; + debug("CancelationTokens < "+canceledAllBefore+" are Canceled"); + } + + private void debug(String string) { + if (DEBUG!=null) { + System.out.println(DEBUG+": "+string); + } + } +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ClientRequests.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ClientRequests.java new file mode 100644 index 000000000..79d8f67dc --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ClientRequests.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2015, 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFApplication; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFApplicationDetail; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFCloudDomain; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFSpace; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack; +import org.springframework.ide.vscode.commons.cloudfoundry.client.SshClientSupport; +import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CancelationTokens.CancelationToken; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface ClientRequests { + +// /** +// * The actual Rest API version that cloud controller claims to be. +// */ +// Version getApiVersion(); +// +// /** +// * The minimum version that the CF V2 java client claims to support. +// */ +// Version getSupportedApiVersion(); + + /** + * Returns null if the application does not exist. Throws some kind of Exception if there's any other kind of problem. + */ + CFApplicationDetail getApplication(String appName) throws Exception; + + //TODO: consider removing the getXXXSupport method and directly adding the apis that these support + // objects provide. + SshClientSupport getSshClientSupport() throws Exception; + + + void deleteApplication(String name) throws Exception; + void logout(); + + List getApplicationsWithBasicInfo() throws Exception; + List getBuildpacks() throws Exception; + List getDomains() throws Exception; + List getServices() throws Exception; + List getSpaces() throws Exception; + List getStacks() throws Exception; + void restartApplication(String appName, CancelationToken token) throws Exception; + void stopApplication(String appName) throws Exception; + Flux getApplicationDetails(List appsToLookUp) throws Exception; + String getHealthCheck(UUID appGuid) throws Exception; + void setHealthCheck(UUID guid, String hcType) throws Exception; + boolean applicationExists(String appName) throws Exception; + + //Removed in V2 + //void createApplication(CloudApplicationDeploymentProperties deploymentProperties) throws Exception; + + //Added since v2: + void push(CFPushArguments args, CancelationToken cancelationToken) throws Exception; + Map getApplicationEnvironment(String appName) throws Exception; + Mono deleteServiceAsync(String serviceName); + + /** + * Gets current value of the client's refresh token. Note that the token is only set once it is known. + * Initially, if a client is created via password auth, then the refreshToken won't be known until + * some operation has been executed. + * + * @return Refresh token if it is already known, null otherwise. + */ + String getRefreshToken(); + Mono getUserName(); +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java new file mode 100644 index 000000000..1763217ce --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java @@ -0,0 +1,250 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.reactor.ConnectionContext; +import org.cloudfoundry.reactor.DefaultConnectionContext; +import org.cloudfoundry.reactor.ProxyConfiguration; +import org.cloudfoundry.reactor.TokenProvider; +import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient; +import org.cloudfoundry.reactor.doppler.ReactorDopplerClient; +import org.cloudfoundry.reactor.tokenprovider.OneTimePasscodeTokenProvider; +import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider; +import org.cloudfoundry.reactor.tokenprovider.RefreshTokenGrantTokenProvider; +import org.cloudfoundry.reactor.uaa.ReactorUaaClient; +import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFCredentials; + +/** + * TODO: Remove this class when the 'thread leak bug' in V2 client is fixed. + * + * At the moment each time {@link SpringCloudFoundryClient} is create a threadpool + * is created by the client and it is never cleaned up. The only way we have + * to mitigate this leak is to try and create as few clients as possible. + *

+ * So we have a permanent cache of clients here that is reused. + *

+ * When the bug is fixed then this should no longer be necessary and we can removed this cache + * and just create the client as needed. + * + * @author Kris De Volder + */ +public class CloudFoundryClientCache { + + public class CFClientProvider { + + final ConnectionContext connection; + final TokenProvider tokenProvider; + + //Note the three client objects below are 'stateless' wrappers and it would be + // fine to recreate as needed instead of store them + + final CloudFoundryClient client; + final ReactorUaaClient uaaClient; + final ReactorDopplerClient doppler; + + private ProxyConfiguration getProxy(String host) { + // TODO: enable proxy support for vscode. The code below retrieves proxy via Eclipse proxy service thus + // not applicable when this is used outside of Eclipse +// try { +// if (StringUtils.hasText(host)) { +// URL url = new URL("https://"+host); +// // In certain cases, the activator would have stopped and the plugin may +// // no longer be available. Usually onl happens on shutdown. +// BootDashActivator plugin = BootDashActivator.getDefault(); +// if (plugin != null) { +// IProxyService proxyService = plugin.getProxyService(); +// if (proxyService != null) { +// IProxyData[] selectedProxies = proxyService.select(url.toURI()); +// +// // No proxy configured or not found +// if (selectedProxies == null || selectedProxies.length == 0) { +// return null; +// } +// +// IProxyData data = selectedProxies[0]; +// int proxyPort = data.getPort(); +// String proxyHost = data.getHost(); +// String user = data.getUserId(); +// String password = data.getPassword(); +// if (proxyHost!=null) { +// return ProxyConfiguration.builder() +// .host(proxyHost) +// .port(proxyPort==-1?Optional.empty():Optional.of(proxyPort)) +// .username(Optional.ofNullable(user)) +// .password(Optional.ofNullable(password)) +// .build(); +//// return proxyHost != null ? new HttpProxyConfiguration(proxyHost, proxyPort, +//// data.isRequiresAuthentication(), user, password) : null; +// } +// } +// } +// } +// } catch (Exception e) { +// Log.log(e); +// } + return null; + } + + public CFClientProvider(Params params) { + long sslTimeout = Long.getLong("sts.bootdash.cf.client.ssl.handshake.timeout", 60); //TODO: make a preference for this? + Optional keepAlive = getBooleanSystemProp("http.keepAlive"); + debug("cf client keepAlive = "+keepAlive); + connection = DefaultConnectionContext.builder() + .proxyConfiguration(Optional.ofNullable(getProxy(params.host))) + .apiHost(params.host) + .sslHandshakeTimeout(Duration.ofSeconds(sslTimeout)) + .keepAlive(keepAlive) + .skipSslValidation(params.skipSsl) + .build(); + + tokenProvider = createTokenProvider(params); + + client = ReactorCloudFoundryClient.builder() + .connectionContext(connection) + .tokenProvider(tokenProvider) + .build(); + + uaaClient = ReactorUaaClient.builder() + .connectionContext(connection) + .tokenProvider(tokenProvider) + .build(); + + doppler = ReactorDopplerClient.builder() + .connectionContext(connection) + .tokenProvider(tokenProvider) + .build(); + } + + private TokenProvider createTokenProvider(Params params) { + CFCredentials creds = params.credentials; + switch (creds.getType()) { + case PASSWORD: + return PasswordGrantTokenProvider.builder() + .username(params.username) + .password(creds.getSecret()) + .build(); + case REFRESH_TOKEN: + return RefreshTokenGrantTokenProvider.builder() + .token(creds.getSecret()) + .build(); + case TEMPORARY_CODE: + return OneTimePasscodeTokenProvider.builder() + .passcode(creds.getSecret()) + .build(); + default: + throw new IllegalStateException("BUG! Missing switch case?"); + } + } + + private Optional getBooleanSystemProp(String name) { + String str = System.getProperty(name); + if (str!=null) { + return Optional.of(Boolean.valueOf(str)); + } + return Optional.empty(); + } + } + + private static final boolean DEBUG = true; + + private static void debug(String string) { + if (DEBUG) { + System.out.println(string); + } + } + + public static class Params { + public final String username; + public final CFCredentials credentials; + public final String host; + public final boolean skipSsl; + public Params(String username, CFCredentials credentials, String host, boolean skipSsl) { + super(); + this.username = username; + this.credentials = credentials; + this.host = host; + this.skipSsl = skipSsl; + } + + @Override + public String toString() { + return "Params [username=" + username + ", host=" + host + ", skipSsl=" + skipSsl + + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((host == null) ? 0 : host.hashCode()); + result = prime * result + ((credentials == null) ? 0 : credentials.hashCode()); + result = prime * result + (skipSsl ? 1231 : 1237); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Params other = (Params) obj; + if (host == null) { + if (other.host != null) + return false; + } else if (!host.equals(other.host)) + return false; + if (credentials == null) { + if (other.credentials != null) + return false; + } else if (!credentials.equals(other.credentials)) + return false; + if (skipSsl != other.skipSsl) + return false; + if (username == null) { + if (other.username != null) + return false; + } else if (!username.equals(other.username)) + return false; + return true; + } + } + + private Map cache = new HashMap<>(); + + private int clientCount = 0; + + public synchronized CFClientProvider getOrCreate(String username, CFCredentials credentials, String host, boolean skipSsl) { + Params params = new Params(username, credentials, host, skipSsl); + CFClientProvider client = cache.get(params); + if (client==null) { + clientCount++; + debug("Creating client ["+clientCount+"]: "+params); + cache.put(params, client = create(params)); + } else { + debug("Reusing client ["+clientCount+"]: "+params); + } + return client; + } + + protected CFClientProvider create(Params params) { + return new CFClientProvider(params); + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java new file mode 100644 index 000000000..53f082353 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java @@ -0,0 +1,1330 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import static org.cloudfoundry.util.tuple.TupleUtils.function; + +import java.io.IOException; +import java.time.Duration; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.v2.applications.ApplicationEntity; +import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; +import org.cloudfoundry.client.v2.applications.GetApplicationResponse; +import org.cloudfoundry.client.v2.applications.UpdateApplicationRequest; +import org.cloudfoundry.client.v2.applications.UpdateApplicationResponse; +import org.cloudfoundry.client.v2.buildpacks.ListBuildpacksRequest; +import org.cloudfoundry.client.v2.buildpacks.ListBuildpacksResponse; +import org.cloudfoundry.client.v2.domains.DomainResource; +import org.cloudfoundry.client.v2.domains.ListDomainsRequest; +import org.cloudfoundry.client.v2.domains.ListDomainsResponse; +import org.cloudfoundry.client.v2.info.GetInfoRequest; +import org.cloudfoundry.client.v2.info.GetInfoResponse; +import org.cloudfoundry.client.v2.serviceinstances.DeleteServiceInstanceRequest; +import org.cloudfoundry.client.v2.stacks.GetStackRequest; +import org.cloudfoundry.client.v2.stacks.GetStackResponse; +import org.cloudfoundry.client.v2.userprovidedserviceinstances.DeleteUserProvidedServiceInstanceRequest; +import org.cloudfoundry.operations.CloudFoundryOperations; +import org.cloudfoundry.operations.DefaultCloudFoundryOperations; +import org.cloudfoundry.operations.applications.ApplicationDetail; +import org.cloudfoundry.operations.applications.DeleteApplicationRequest; +import org.cloudfoundry.operations.applications.GetApplicationEnvironmentsRequest; +import org.cloudfoundry.operations.applications.GetApplicationRequest; +import org.cloudfoundry.operations.applications.RestartApplicationRequest; +import org.cloudfoundry.operations.applications.StartApplicationRequest; +import org.cloudfoundry.operations.applications.StopApplicationRequest; +import org.cloudfoundry.operations.organizations.OrganizationDetail; +import org.cloudfoundry.operations.organizations.OrganizationInfoRequest; +import org.cloudfoundry.operations.organizations.OrganizationSummary; +import org.cloudfoundry.operations.routes.ListRoutesRequest; +import org.cloudfoundry.operations.routes.MapRouteRequest; +import org.cloudfoundry.operations.routes.UnmapRouteRequest; +import org.cloudfoundry.operations.services.BindServiceInstanceRequest; +import org.cloudfoundry.operations.services.CreateServiceInstanceRequest; +import org.cloudfoundry.operations.services.CreateUserProvidedServiceInstanceRequest; +import org.cloudfoundry.operations.services.GetServiceInstanceRequest; +import org.cloudfoundry.operations.services.ServiceInstance; +import org.cloudfoundry.operations.services.UnbindServiceInstanceRequest; +import org.cloudfoundry.operations.spaces.GetSpaceRequest; +import org.cloudfoundry.operations.spaces.SpaceDetail; +import org.cloudfoundry.reactor.tokenprovider.AbstractUaaTokenProvider; +import org.cloudfoundry.uaa.UaaClient; +import org.cloudfoundry.util.PaginationUtils; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFApplication; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFApplicationDetail; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFCloudDomain; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFSpace; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack; +import org.springframework.ide.vscode.commons.cloudfoundry.client.SshClientSupport; +import org.springframework.ide.vscode.commons.cloudfoundry.client.SshHost; +import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFClientParams; +import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CancelationTokens.CancelationToken; +import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CloudFoundryClientCache.CFClientProvider; +import org.springframework.ide.vscode.commons.util.ExceptionUtil; +import org.springframework.util.StringUtils; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * @author Kris De Volder + * @author Nieraj Singh + */ +public class DefaultClientRequestsV2 implements ClientRequests { + + private static final Duration APP_START_TIMEOUT = Duration.ofMillis(60*10); + private static final Duration GET_SERVICES_TIMEOUT = Duration.ofSeconds(60); + private static final Duration GET_SPACES_TIMEOUT = Duration.ofSeconds(20); + private static final Duration GET_USERNAME_TIMEOUT = Duration.ofSeconds(5); + private static final Logger logger = Logger.getLogger(DefaultClientRequestsV2.class.getName()); + private static final boolean DEBUG = false; + + + +// static { +// if (DEBUG_REACTOR) { +// Loggers.enableExtension(new Extension() { +// @Override +// public void log(String category, java.util.logging.Level level, String msg, Object... arguments) { +// debug(category +"["+level + "] : "+MessageFormatter.format(msg, arguments).getMessage()); +// } +// }); +// } +// } + + +// TODO: it would be good not to create another 'threadpool' and use something like the below code +// instead so that eclipse job scheduler is used for reactor 'tasks'. However... the code below +// may not be 100% correct. +// private static final Callable> SCHEDULER_GROUP = () -> { +// return (Runnable task) -> { +// Job job = new Job("CF Client background task") { +// @Override +// protected IStatus run(IProgressMonitor monitor) { +// if (task!=null) { +// task.run(); +// } +// return Status.OK_STATUS; +// } +// }; +// job.setRule(JobUtil.lightRule("reactor-job-rule")); +// job.setSystem(true); +// job.schedule(); +// }; +// }; + + + private CFClientParams params; + private CloudFoundryClient _client ; + private UaaClient _uaa; + private CloudFoundryOperations _operations; + + private Mono orgId; + private Mono info; + private Mono spaceId; + private AbstractUaaTokenProvider _tokenProvider; + + public DefaultClientRequestsV2(CloudFoundryClientCache clients, CFClientParams params) { + this.params = params; + CFClientProvider provider = clients.getOrCreate(params.getUsername(), params.getCredentials(), params.getHost(), params.skipSslValidation()); + this._client = provider.client; + this._uaa = provider.uaaClient; + this._tokenProvider = (AbstractUaaTokenProvider) provider.tokenProvider; + this._operations = DefaultCloudFoundryOperations.builder() + .cloudFoundryClient(_client) + .dopplerClient(provider.doppler) + .uaaClient(provider.uaaClient) + .organization(params.getOrgName()) + .space(params.getSpaceName()) + .build(); + this.orgId = getOrgId(); + this.spaceId = getSpaceId(); + this.info = client_getInfo().cache(); + } + + private Mono client_createOperations(OrganizationSummary org) { + return log("client.createOperations(org="+org.getName()+")", + Mono.fromCallable(() -> DefaultCloudFoundryOperations.builder() + .cloudFoundryClient(_client) + .organization(org.getName()) + .build() + ) + ); + } + + private Mono getOrgId() { + String orgName = params.getOrgName(); + if (orgName==null) { + return Mono.error(new IOException("No organization targetted")); + } else { + return operations_getOrgId().cache(); + } + } + + private Mono getSpaceId() { + String spaceName = params.getSpaceName(); + if (spaceName==null) { + return Mono.error(new IOException("No space targetted")); + } else { + return _operations.spaces().get(GetSpaceRequest.builder() + .name(params.getSpaceName()) + .build() + ) + .map(SpaceDetail::getId) + .cache(); + } + } + + @Override + public List getApplicationsWithBasicInfo() throws Exception { + return ReactorUtils.get(operations_listApps()); + } + + private ApplicationExtras getApplicationExtras(String appName) { + //Stuff used in computing the 'extras'... + Mono appIdMono = getApplicationId(appName); + Mono entity = appIdMono + .then((appId) -> + client_getApplication(appId) + ) + .map((appResource) -> appResource.getEntity()) + .cache(); + + //The stuff returned from the getters of 'extras'... + Mono> services = prefetch("services", getBoundServicesList(appName)); + Mono> env = prefetch("env", + DefaultClientRequestsV2.this.getEnv(appName) + ); + Mono buildpack = prefetch("buildpack", + entity.then((e) -> Mono.justOrEmpty(e.getBuildpack())) + ); + + Mono stack = prefetch("stack", + entity.then((e) -> Mono.justOrEmpty(e.getStackId())) + .then((stackId) -> { + return client_getStack(stackId); + }).map((response) -> { + return response.getEntity().getName(); + }) + ); + Mono timeout = prefetch("timeout", + entity + .then((v) -> Mono.justOrEmpty(v.getHealthCheckTimeout())) + ); + + Mono command = prefetch("command", + entity.then((e) -> Mono.justOrEmpty(e.getCommand())) + ); + + Mono healthCheckType = prefetch("healthCheckType", + entity.then((e) -> Mono.justOrEmpty(e.getHealthCheckType())) + ); + + return new ApplicationExtras() { + @Override + public Mono> getServices() { + return services; + } + + @Override + public Mono> getEnv() { + return env; + } + + @Override + public Mono getBuildpack() { + return buildpack; + } + + @Override + public Mono getStack() { + return stack; + } + + public Mono getTimeout() { + return timeout; + } + + @Override + public Mono getCommand() { + return command; + } + + @Override + public Mono getHealthCheckType() { + return healthCheckType; + } + }; + } + + private Mono prefetch(String id, Mono toFetch) { + return toFetch +// .log(id + " before error handler") + .otherwise((error) -> { + logger.log(Level.SEVERE,"Failed prefetch '"+id+"'", error); + return Mono.empty(); + }) +// .log(id + " after error handler") + .cache() +// .log(id + "after cache") + ; + } + +// private Mono prefetch(Mono toFetch) { +// Mono result = toFetch +// .cache(); // It should only be fetched once. +// +// //We must ensure the 'result' is being consumed by something to force its execution: +// result +// .publishOn(SCHEDULER_GROUP) //Ensure the consume is truly async or it may block here. +// .consume((dont_care) -> {}); +// +// return result; +// } + + @Override + public List getServices() throws Exception { + return ReactorUtils.get(GET_SERVICES_TIMEOUT, CancelationTokens.NULL, + log("operations.services.listInstances()", + _operations + .services() + .listInstances() + .map(CFWrappingV2::wrap) + .collectList() + .map(ImmutableList::copyOf) + ) + ); + } + + /** + * Get details for a given list of applications. This does a 'best' effort getting the details for + * as many apps as possible but it does not guarantee that it will return details for each app in the + * list. This is to avoid one 'bad apple' from spoiling the whole batch. (I.e if failing to fetch details for + * some apps we can still return details for the others rather than throw an exception). + */ + @Override + public Flux getApplicationDetails(List appsToLookUp) throws Exception { + return Flux.fromIterable(appsToLookUp) + .flatMap((CFApplication appSummary) -> { + return getApplicationDetail(appSummary.getName()) + .otherwise((error) -> { + logger.log(Level.SEVERE,"getting application details for '"+appSummary.getName()+"' failed", error); + + return Mono.empty(); + }) + .map((ApplicationDetail appDetails) -> CFWrappingV2.wrap((CFApplicationSummaryData)appSummary, appDetails)); + }); + } + + + /** + * Creates a retry 'signal factory' to be used with Flux.retryWhen. + *

+ * @param timeBetween How much time to wait before retrying after a failure + * @param duration If this much time has elapsed when the error happens there will be no further retries. + * @return Functon that can be passed to retryWhen. + */ + private Function, Flux> retryInterval(Duration timeBetween, Duration duration) { + Predicate falseAfterDuration = falseAfter(duration); + return (errors) -> { + return errors.flatMap((error) -> { + if (falseAfterDuration.test(error)) { + return Mono.delay(timeBetween); + } else { + return Mono.error(error); + } + }); + }; + } + + private Predicate falseAfter(Duration timeToWait) { + return new Predicate() { + + private Long firstCalledAt; + + @Override + public boolean test(Throwable t) { + if (firstCalledAt==null) { + firstCalledAt = System.currentTimeMillis(); + } + long waitedTime = System.currentTimeMillis() - firstCalledAt; + //debug("falseAfter: remaining = "+(timeToWait.toMillis() - waitedTime)); + return waitedTime < timeToWait.toMillis(); + } + + }; + } + + @Override + public void stopApplication(String appName) throws Exception { + ReactorUtils.get( + stopApp(appName) + ); + } + + private Mono stopApp(String appName) { + return log("operations.applications.stop(name="+appName+")", + _operations.applications().stop(StopApplicationRequest.builder() + .name(appName) + .build() + ) + ); + } + + @Override + public void restartApplication(String appName, CancelationToken cancelationToken) throws Exception { + ReactorUtils.get(APP_START_TIMEOUT, cancelationToken, + restartApp(appName) + ); + } + + private Mono restartApp(String appName) { + return log("operations.applications().restart(name="+appName+")", + _operations.applications().restart(RestartApplicationRequest.builder() + .name(appName) + .build()) + ); + } + + @Override + public void logout() { + _operations = null; + _client = null; + } + + public boolean isLoggedOut() { + return _client==null; + } + + @Override + public List getStacks() throws Exception { + return ReactorUtils.get( + log("operations.stacks().list()", + _operations.stacks() + .list() + .map(CFWrappingV2::wrap) + .collectList() + ) + ); + } + + @Override + public SshClientSupport getSshClientSupport() throws Exception { + return new SshClientSupport() { + + @Override + public String getSshUser(UUID appGuid, int instance) { + return "cf:"+appGuid+"/" + instance; + } + + @Override + public String getSshUser(String appName, int instance) throws Exception { + return ReactorUtils.get( + getApplicationId(appName) + .map((guid) -> getSshUser(guid, instance)) + ); + } + + @Override + public SshHost getSshHost() throws Exception { + return ReactorUtils.get( + info.then((i) -> { + String fingerPrint = i.getApplicationSshHostKeyFingerprint(); + String host = i.getApplicationSshEndpoint(); + int port = 22; //Default ssh port + if (host!=null) { + if (host.contains(":")) { + String[] pieces = host.split(":"); + host = pieces[0]; + port = Integer.parseInt(pieces[1]); + } + } + if (host!=null) { + return Mono.just(new SshHost(host, port, fingerPrint)); + } + // Workaround for bug in Eclipse Neon.1 JDT cannot properly infer type SshHost. + // Works in Mars. Returning Mono.empty() results in compilation error in Neon.1 + return Mono.empty(); + }) + ); + } + + @Override + public String getSshCode() throws Exception { + return ReactorUtils.get( + log("operations.advanced.sshCode()", + _operations.advanced().sshCode() + ) + ); + } + }; + } + + private Mono operationsFor(OrganizationSummary org) { + return client_createOperations(org); + } + + @Override + public List getSpaces() throws Exception { + Object it = ReactorUtils.get(GET_SPACES_TIMEOUT, log("operations.organizations().list()", + _operations.organizations() + .list() + ) + .flatMap((OrganizationSummary org) -> { + return operationsFor(org).flatMap((operations) -> + log("operations.spaces.list(org="+org.getId()+")", + operations + .spaces() + .list() + .map((space) -> CFWrappingV2.wrap(org, space)) + ) + ); + }) + .collectList() + ); + //workaround eclipse jdt bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=501949 + return (List) it; + } + + @Override + public String getHealthCheck(UUID appGuid) throws Exception { + //XXX CF V2: getHealthcheck (via operations API) + // See: https://www.pivotaltracker.com/story/show/116462215 + return ReactorUtils.get( + client_getApplication(appGuid) + .map((response) -> response.getEntity().getHealthCheckType()) + ); + } + + @Override + public void setHealthCheck(UUID guid, String hcType) throws Exception { + //XXX CF V2: setHealthCheck (via operations API) + // See: https://www.pivotaltracker.com/story/show/116462369 + ReactorUtils.get( + client_setHealthCheck(guid, hcType) + ); + } + + @Override + public List getDomains() throws Exception { + //XXX CF V2: list domains using 'operations' api. + return ReactorUtils.get(Duration.ofMinutes(2), + orgId.flatMap(this::requestDomains) + .map(CFWrappingV2::wrap) + .collectList() + ); + } + + private Flux requestDomains(String orgId) { + return PaginationUtils.requestClientV2Resources((page) -> + client_listDomains(page) + ); + } + + @Override + public List getBuildpacks() throws Exception { + //XXX CF V2: getBuilpacks using 'operations' API. + return ReactorUtils.get( + PaginationUtils.requestClientV2Resources((page) -> { + return client_listBuildpacks(page); + }) + .map(CFWrappingV2::wrap) + .collectList() + ); + } + + @Override + public CFApplicationDetail getApplication(String appName) throws Exception { + return ReactorUtils.get( + getApplicationMono(appName) + // .log("getApplication("+appName+")") + ); + } + + private Mono getApplicationMono(String appName) { + return getApplicationDetail(appName) + .map((appDetail) -> { + //TODO: we have 'real' appdetails now so we could get most of the 'application extras' info from that. + return CFWrappingV2.wrap(appDetail, getApplicationExtras(appName)); + }) + .otherwise(ReactorUtils.suppressException(IllegalArgumentException.class)); + } + + + @Override + public void deleteApplication(String appName) throws Exception { + ReactorUtils.get( + log("operations.applications().delete(name="+appName+")", + _operations.applications().delete(DeleteApplicationRequest + .builder() + .name(appName) + .build() + ) + ) + ); + } + + @Override + public boolean applicationExists(String appName) throws Exception { + return ReactorUtils.get( + getApplicationMono(appName) + .map((app) -> true) + .otherwiseIfEmpty(Mono.just(false)) + ); + } + + public Mono ifApplicationExists(String appName, Function> then, Mono els) throws Exception { + return getApplicationDetail(appName) + .map((app) -> Optional.of(app)) + .otherwiseIfEmpty(Mono.just(Optional.empty())) + .otherwise((error) -> Mono.just(Optional.empty())) + .then((Optional app) -> { + if (app.isPresent()) { + return then.apply(app.get()); + } else { + return els; + } + }); + } + + @Override + public void push(CFPushArguments params, CancelationToken cancelationToken) throws Exception { + String appName = params.getAppName(); + ReactorUtils.get(APP_START_TIMEOUT, cancelationToken, + ifApplicationExists(appName, + ((app) -> pushExisting(app, params)), + firstPush(params) + ) + ); + } + + private Mono pushExisting(ApplicationDetail app, CFPushArguments params) { + String appName = params.getAppName(); + UUID appId = UUID.fromString(app.getId()); + return updateApp(appId, params) + .then(getApplicationDetail(appName)) + .then((appDetail) -> { + return Flux.merge( + setRoutes(appDetail, params.getRoutes()), + bindAndUnbindServices(appName, params.getServices()) + ).then(); + }) + .then(mono_debug("Uploading[1]...")) + .then(Mono.fromCallable(() -> { + debug("Uploading[2]..."); +// v1().uploadApplication(appName, params.getApplicationData()); + debug("Uploading[2] DONE"); + return "who cares"; + })) + .then(mono_debug("Uploading[1] DONE")) + .then(params.isNoStart() + ? stopApp(appName) + : restartApp(appName) + ); + } + +// private DefaultClientRequestsV1 v1() throws Exception { +// if (_v1==null) { +// CFClientParams v1params = new CFClientParams( +// params.getApiUrl(), +// params.getUsername(), +// CFCredentials.fromRefreshToken(getRefreshToken()), +// params.isSelfsigned(), +// params.getOrgName(), +// params.getSpaceName(), +// params.skipSslValidation() +// ); +// _v1 = new DefaultClientRequestsV1(v1params); +// } +// return _v1; +// } + + private Mono mono_debug(String string) { + return Mono.fromRunnable(() -> debug(string)); + } + + private Mono firstPush(CFPushArguments params) { + String appName = params.getAppName(); + return createApp(params) + .then(getApplicationDetail(appName)) + .then((appDetail) -> + Flux.merge( + setRoutes(appDetail, params.getRoutes()), + bindAndUnbindServices(appName, params.getServices()) + ).then() + ) + .then(Mono.fromCallable(() -> { +// v1().uploadApplication(appName, params.getApplicationData()); + return "who cares"; + })) + .then(params.isNoStart() + ? Mono.empty() + : startApp(appName) + ) + .then(); + } + + private Mono createApp(CFPushArguments params) { + return spaceId.and(getStackId(params)) + .then(function((spaceId, stackId) -> { + CreateApplicationRequest req = CreateApplicationRequest.builder() + .spaceId(spaceId) + .stackId(stackId.orElse(null)) + .name(params.getAppName()) + .memory(params.getMemory()) + .diskQuota(params.getDiskQuota()) + .healthCheckType(params.getHealthCheckType()) + .healthCheckTimeout(params.getTimeout()) + .buildpack(params.getBuildpack()) + .command(params.getCommand()) + .environmentJsons(params.getEnv()) + .instances(params.getInstances()) + .build(); + return log("client.applications.create("+req+")", + _client.applicationsV2().create(req) + ); + })) + .map(response -> UUID.fromString(response.getMetadata().getId())); + } + + private Mono> getStackId(CFPushArguments params) { + String stackName = params.getStack(); + if (stackName==null) { + return Mono.just(Optional.empty()); + } else { + return log("operations.stacks.get("+stackName+")", + _operations.stacks().get(org.cloudfoundry.operations.stacks.GetStackRequest.builder() + .name(stackName) + .build() + ) + .map((stack) -> Optional.of(stack.getId())) + ); + } + } + + + private Mono updateApp(UUID appId, CFPushArguments params) { + return getStackId(params) + .then((stackId) -> { + UpdateApplicationRequest req = UpdateApplicationRequest.builder() + .applicationId(appId.toString()) + .name(params.getAppName()) + .memory(params.getMemory()) + .diskQuota(params.getDiskQuota()) + .healthCheckType(params.getHealthCheckType()) + .healthCheckTimeout(params.getTimeout()) + .buildpack(params.getBuildpack()) + .command(params.getCommand()) + .stackId(stackId.orElse(null)) + .environmentJsons(params.getEnv()) + .instances(params.getInstances()) + .build(); + return log("client.applications.update("+req+")", + _client.applicationsV2().update(req) + ); + }) + .then(Mono.just(appId)); + } + +// public void pushV2(CFPushArguments params, CancelationToken cancelationToken) throws Exception { +// debug("Pushing app starting: "+params.getAppName()); +// //XXX CF V2: push should use 'manifest' in a future version of V2 +// PushApplicationRequest pushRequest = toPushRequest(params) +// .noStart(true) +// .noRoute(true) +// .build(); +// ReactorUtils.get(APP_START_TIMEOUT, cancelationToken, +// log("operations.applications().push("+pushRequest+")", +// _operations.applications() +// .push(pushRequest) +// ) +// .then(getApplicationDetail(params.getAppName())) +// .then(appDetail -> { +// return Flux.merge( +// setRoutes(appDetail, params.getRoutes()), +// setEnvVars(appDetail, params.getEnv()), +// bindAndUnbindServices(params.getAppName(), params.getServices()) +// ) +// .then(); +// }) +// .then(() -> { +// if (!params.isNoStart()) { +// return startApp(params.getAppName()); +// } else { +// return Mono.empty(); +// } +// }) +// ); +// debug("Pushing app succeeded: "+params.getAppName()); +// } + + private Mono getApplicationDetail(String appName) { + return log("operations.applications.get(name="+appName+")", + _operations.applications().get(GetApplicationRequest.builder() + .name(appName) + .build() + ) + ); + } + + public Mono setRoutes(ApplicationDetail appDetails, Collection desiredUrls) { + debug("setting routes for '"+appDetails.getName()+"': "+desiredUrls); + + //Carefull! It is not safe map/unnmap multiple routes in parallel. Doing so causes some of the + // operations to fail, presumably because of some 'optimisitic locking' being used in the database + // that keeps track of routes. + //To avoid this problem we must execute all that map / unmap calls in sequence! + return ReactorUtils.sequence( + unmapUndesiredRoutes(appDetails.getName(), desiredUrls), + mapDesiredRoutes(appDetails, desiredUrls) + ); + } + + public Mono setRoutes(String appName, Collection desiredUrls) { + return getApplicationDetail(appName) + .then(appDetails -> setRoutes(appDetails, desiredUrls)); + } + + private Mono mapDesiredRoutes(ApplicationDetail appDetail, Collection desiredUrls) { + Set currentUrls = ImmutableSet.copyOf(appDetail.getUrls()); + Mono> domains = getDomainNames().cache(); + String appName = appDetail.getName(); + + debug("currentUrls = "+currentUrls); + return Flux.fromIterable(desiredUrls) + .flatMap((url) -> { + if (currentUrls.contains(url)) { + debug("skipping: "+url); + return Mono.empty(); + } else { + debug("mapping: "+url); + return mapRoute(domains, appName, url); + } + }, 1) //!!!IN SEQUENCE!!! + .then(); + } + + private Mono mapRoute(Mono> domains, String appName, String desiredUrl) { + debug("mapRoute: "+appName+" -> "+desiredUrl); + return toRoute(domains, desiredUrl) + .then((CFRoute route) -> mapRoute(appName, route)) + .doOnError((e) -> { + logger.info("mapRoute FAILED!"); + }); + } + + private Mono mapRoute(String appName, CFRoute route) { + MapRouteRequest mapRouteReq = MapRouteRequest.builder() + .applicationName(appName) + .domain(route.getDomain()) + .host(route.getHost()) + .path(route.getPath()) + .build(); + return log("operations.routes.map("+mapRouteReq+")", + _operations.routes().map(mapRouteReq) + ); + } + + private Mono toRoute(Mono> domains, String desiredUrl) { + return domains.then((ds) -> { + for (String d : ds) { + //TODO: we assume that there's no 'path' component for now, which simpiflies things. What if there is a path component? + if (desiredUrl.endsWith(d)) { + String host = desiredUrl.substring(0, desiredUrl.length()-d.length()); + while (host.endsWith(".")) { + host = host.substring(0, host.length()-1); + } + CFRoute.Builder route = CFRoute.builder(); + route.domain(d); + if (StringUtils.hasText(host)) { + route.host(host); + } + return Mono.just(route.build()); + } + } + return Mono.error(new IOException("Couldn't find a domain matching "+desiredUrl)); + }); + } + + private Mono> getDomainNames() { + return orgId.flatMap(this::requestDomains) + .map((r) -> r.getEntity().getName()) + .collectList() + .map(ImmutableSet::copyOf); + } + +// private Set getUrls(ApplicationDetail app) { +// return operations.applications().get(GetApplicationRequest.builder() +// .name(appName) +// .build() +// ) +// .map((app) -> ImmutableSet.copyOf(app.getUrls())); +// } + + private Mono unmapUndesiredRoutes(String appName, Collection desiredUrls) { + return getExistingRoutes(appName) + .flatMap((route) -> { + debug("unmap? "+route); + if (desiredUrls.contains(getUrl(route))) { + debug("unmap? "+route+" SKIP"); + return Mono.empty(); + } else { + debug("unmap? "+route+" UNMAP"); + return unmapRoute(appName, route); + } + }, 1) //!!!IN SEQUENCE!!! + .then(); + } + + private String getUrl(CFRoute route) { + String url = route.getDomain(); + if (route.getHost()!=null) { + url = route.getHost() + "." + url; + } + String path = route.getPath(); + if (path!=null) { + while (path.startsWith("/")) { + path = path.substring(1); + } + if (StringUtils.hasText(path)) { + url = url +"/" +path; + } + } + return url; + } + + private Mono unmapRoute(String appName, CFRoute route) { + String path = route.getPath(); +// if (!StringUtil.hasText(path)) { +// //client doesn't like to get 'empty string' it will complain that route doesn't exist. +// path = null; +// } + UnmapRouteRequest req = UnmapRouteRequest.builder() + .applicationName(appName) + .domain(route.getDomain()) + .host(route.getHost()) + .path(path) + .build(); + return log("operations.routes.unmap("+req+")", + _operations.routes().unmap(req) + ); + } + + public Flux getExistingRoutes(String appName) { + return log("operations.routes.list(level=SPACE)", + _operations.routes().list(ListRoutesRequest.builder() + .level(org.cloudfoundry.operations.routes.Level.SPACE) + .build() + ) + ) + .flatMap((route) -> { + for (String app : route.getApplications()) { + if (app.equals(appName)) { + return Mono.just(new CFRoute(route)); + } + }; + return Mono.empty(); + }); + } + +// private static PushApplicationRequest.Builder toPushRequest(CFPushArguments params) { +// return PushApplicationRequest.builder() +// .name(params.getAppName()) +// .memory(params.getMemory()) +// .diskQuota(params.getDiskQuota()) +// .timeout(params.getTimeout()) +// .buildpack(params.getBuildpack()) +// .command(params.getCommand()) +// .stack(params.getStack()) +// .instances(params.getInstances()) +// .application(params.getApplicationData()); +// } + + public Mono bindAndUnbindServices(String appName, List _services) { + debug("bindAndUnbindServices "+_services); + Set services = ImmutableSet.copyOf(_services); + return getBoundServicesSet(appName) + .flatMap((boundServices) -> { + debug("boundServices = "+boundServices); + Set toUnbind = Sets.difference(boundServices, services); + Set toBind = Sets.difference(services, boundServices); + debug("toBind = "+toBind); + debug("toUnbind = "+toUnbind); + return Flux.merge( + bindServices(appName, toBind), + unbindServices(appName, toUnbind) + ); + }) + .then(); + } + + public Flux getBoundServices(String appName) { + return log("operations.services.listInstances()", + _operations.services().listInstances() + ) + .filter((service) -> isBoundTo(service, appName)) + .map(ServiceInstance::getName); + } + + public Mono> getBoundServicesSet(String appName) { + return getBoundServices(appName) + .collectList() + .map(ImmutableSet::copyOf); + } + + public Mono> getBoundServicesList(String appName) { + return getBoundServices(appName) + .collectList() + .map(ImmutableList::copyOf); + } + + private boolean isBoundTo(ServiceInstance service, String appName) { + return service.getApplications().stream() + .anyMatch((boundAppName) -> boundAppName.equals(appName)); + } + + private Flux bindServices(String appName, Set services) { + return Flux.fromIterable(services) + .flatMap((service) -> { + return log("operations.services().bind(appName="+appName+", services="+services+")", + _operations.services().bind(BindServiceInstanceRequest.builder() + .applicationName(appName) + .serviceInstanceName(service) + .build() + ) + ); + }); + } + + private Flux unbindServices(String appName, Set toUnbind) { + return Flux.fromIterable(toUnbind) + .flatMap((service) -> { + return log("operations.services.unbind(appName="+appName+", serviceInstanceName="+service+")", + _operations.services().unbind(UnbindServiceInstanceRequest.builder() + .applicationName(appName) + .serviceInstanceName(service) + .build() + ) + ); + }); + } + + + protected Mono startApp(String appName) { + return log("operations.applications.start(name="+appName+")", + _operations.applications() + .start(StartApplicationRequest.builder() + .name(appName) + .build() + ) + ); + } + + private Mono getApplicationId(String appName) { + return log("operations.applications.get(name="+appName+")", + _operations.applications().get(GetApplicationRequest.builder() + .name(appName) + .build() + ) + ).map((app) -> UUID.fromString(app.getId())); + } + + public Mono setEnvVars(ApplicationDetail appDetail, Map environment) { + return setEnvVars(UUID.fromString(appDetail.getId()), environment); + } + + public Mono setEnvVars(UUID appId, Map environment) { + return client_setEnv(appId, environment); + } + + public Mono setEnvVars(String appName, Map environment) { + return getApplicationId(appName) + .then((applicationId) -> setEnvVars(applicationId, environment)); + } + +// protected Publisher setEnvVar(String appName, String var, String value) { +// System.out.println("Set var starting: "+var +" = "+value); +// return operations.applications() +// .setEnvironmentVariable(SetEnvironmentVariableApplicationRequest.builder() +// .name(appName) +// .variableName(var) +// .variableValue(value) +// .build() +// ) +// .after(() -> { +// System.out.println("Set var complete: "+var +" = "+value); +// return Mono.empty(); +// }); +// } + + public Mono createService(String name, String service, String plan) { + return log("operations.services.createInstance(instanceName="+name+",serviceName="+service+",planName="+plan+")", + _operations.services().createInstance(CreateServiceInstanceRequest.builder() + .serviceInstanceName(name) + .serviceName(service) + .planName(plan) + .build() + ) + ); + } + + public Mono createUserProvidedService(String name, Map credentials) { + return log("operations.services.createUserProvidedInstance(name="+name+")", + _operations.services().createUserProvidedInstance(CreateUserProvidedServiceInstanceRequest.builder() + .name(name) + .credentials(credentials) + .build() + ) + ); + } + +// @Override +// public void deleteService(String serviceName) { +// deleteServiceMono(serviceName).get(); +// } + + @Override + public Mono deleteServiceAsync(String serviceName) { + return getService(serviceName) + .then(this::deleteServiceInstance); + } + + protected Mono deleteServiceInstance(ServiceInstance s) { + switch (s.getType()) { + case MANAGED: + return client_deleteServiceInstance(s); + case USER_PROVIDED: + return client_deleteUserProvidedService(s); + default: + return Mono.error(new IllegalStateException("Unknown service type: "+s.getType())); + } + } + + protected Mono getService(String serviceName) { + return log("operations.services.getInstance(name="+serviceName+")", + _operations.services().getInstance(GetServiceInstanceRequest.builder() + .name(serviceName) + .build() + ) + ); + } + + public Mono> getEnv(String appName) { + return log("operations.applications.getEnvironments(appName="+appName+")", + _operations.applications().getEnvironments(GetApplicationEnvironmentsRequest.builder() + .name(appName) + .build() + ) + ) + .map((envs) -> envs.getUserProvided()) + .map(this::dropObjectsFromMap); + } + + @Override + public Map getApplicationEnvironment(String appName) throws Exception { + return ReactorUtils.get(getEnv(appName)); + } + + private Map dropObjectsFromMap(Map map) { + Builder builder = ImmutableMap.builder(); + for (Entry entry : map.entrySet()) { + try { + builder.put(entry.getKey(), (String) entry.getValue()); + } catch (ClassCastException e) { + logger.log(Level.SEVERE,e.getMessage(), e); + + } + } + return builder.build(); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + //// calls to client and operations with 'logging'. + + private Flux log(String msg, Flux flux) { + if (DEBUG) { + return flux + .doOnSubscribe((sub) -> debug(">>> "+msg)) + .doOnComplete(() -> { + debug("<<< "+msg+" OK"); + }) + .doOnCancel(() -> { + debug("<<< "+msg+" CANCEL"); + }) + .doOnError((error) -> { + debug("<<< "+msg+" ERROR: "+ExceptionUtil.getMessage(error)); + }); + } else { + return flux; + } + } + + private Mono log(String msg, Mono mono) { + if (DEBUG) { + return mono + .doOnSubscribe((sub) -> debug(">>> "+msg)) + .doOnCancel(() -> debug("<<< "+msg+" CANCEL")) + .doOnSuccess((data) -> { + debug("<<< "+msg+" OK"); + }) + .doOnError((error) -> { + debug("<<< "+msg+" ERROR: "+ExceptionUtil.getMessage(error)); + }); + } else { + return mono; + } + } + + private void debug(String string) { + // TODO Auto-generated method stub + + } + + private Mono client_getInfo() { + return log("client.info().get()", + _client.info().get(GetInfoRequest.builder().build()) + ); + } + + private Mono operations_getOrgId() { + return log("operations.organizations.get(name="+params.getOrgName()+")", + _operations.organizations().get(OrganizationInfoRequest.builder() + .name(params.getOrgName()) + .build() + ) + .map(OrganizationDetail::getId) + ); + } + + private Mono> operations_listApps() { + return log("operations.applications.list()", + _operations.applications() + .list() + .map((appSummary) -> + CFWrappingV2.wrap(appSummary, getApplicationExtras(appSummary.getName())) + ) + .collectList() + .map(ImmutableList::copyOf) + ); + } + + private Mono client_getApplication(UUID appId) { + return log("client.applicationsV2.get(id="+appId+")", + _client.applicationsV2() + .get(org.cloudfoundry.client.v2.applications.GetApplicationRequest.builder() + .applicationId(appId.toString()) + .build() + ) + ); + } + + private Mono client_getStack(String stackId) { + return log("client.stacks.get(id="+stackId+")", + _client.stacks().get(GetStackRequest.builder() + .stackId(stackId) + .build() + ) + ); + } + + private Mono client_setHealthCheck(UUID guid, String hcType) { + return log("client.applicationsV2.update(id="+guid+", hcType="+hcType+")", + _client.applicationsV2() + .update(UpdateApplicationRequest.builder() + .applicationId(guid.toString()) + .healthCheckType(hcType) + .build() + ) + ); + } + + private Mono client_listDomains(Integer page) { + return log("client.domains.list(page="+page+")", + _client.domains().list(ListDomainsRequest.builder() + .page(page) + // .owningOrganizationId(orgId) + .build() + ) + ); + } + + private Mono client_listBuildpacks(Integer page) { + return log("client.buildpacks.list(page="+page+")", + _client.buildpacks() + .list(ListBuildpacksRequest.builder() + .page(page) + .build() + ) + ); + } + + private Mono client_setEnv(UUID appId, Map environment) { + return log("client.applicationsV2.update(id="+appId+", env=...)", + _client.applicationsV2() + .update(UpdateApplicationRequest.builder() + .applicationId(appId.toString()) + .environmentJsons(environment) + .build()) + .then() + ); + } + + private Mono client_deleteServiceInstance(ServiceInstance s) { + return log("client.serviceInstances.delete(id="+s.getId()+")", + _client.serviceInstances().delete(DeleteServiceInstanceRequest.builder() + .serviceInstanceId(s.getId()) + .build() + ) + .then() + ); + } + + private Mono client_deleteUserProvidedService(ServiceInstance s) { + return log("client.userProvidedServiceInstances.delete(id="+s.getId()+")", + _client.userProvidedServiceInstances().delete(DeleteUserProvidedServiceInstanceRequest.builder() + .userProvidedServiceInstanceId(s.getId()) + .build() + ) + ); + } + + @Override + public String getRefreshToken() { + return _tokenProvider.getRefreshToken(); + } + + @Override + public Mono getUserName() { + return log("uaa.getUsername", + _uaa.getUsername() + ).timeout(GET_USERNAME_TIMEOUT); + } + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java new file mode 100644 index 000000000..66c737cd1 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFClientParams; + +public class DefaultCloudFoundryClientFactoryV2 { + + public static final DefaultCloudFoundryClientFactoryV2 INSTANCE = new DefaultCloudFoundryClientFactoryV2(); + + /** + * Use 'INSTANCE' constant instead. This class is a singleton. + */ + private DefaultCloudFoundryClientFactoryV2() {} + + private CloudFoundryClientCache cache = new CloudFoundryClientCache(); + + public ClientRequests getClient(CFClientParams params) { + return new DefaultClientRequestsV2(cache, params); + } +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DeploymentProperties.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DeploymentProperties.java new file mode 100644 index 000000000..65cd42ebf --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DeploymentProperties.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Interface for Cloud Foundry application deployment properties + * + * @author Alex Boyko + * + */ +public interface DeploymentProperties { + + int DEFAULT_MEMORY = 1024; + int DEFAULT_INSTANCES = 1; + String DEFAULT_HEALTH_CHECK_TYPE = "port"; + + String getAppName(); + + int getMemory(); + + int getDiskQuota(); + + Integer getTimeout(); + + String getHealthCheckType(); + + String getBuildpack(); + + String getCommand(); + + String getStack(); + + Map getEnvironmentVariables(); + + int getInstances(); + + List getServices(); + + Set getUris(); + +} diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ReactorUtils.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ReactorUtils.java new file mode 100644 index 000000000..53037c797 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ReactorUtils.java @@ -0,0 +1,293 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import java.io.IOException; +import java.time.Duration; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +import org.reactivestreams.Publisher; +import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CancelationTokens.CancelationToken; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +/** + * @author Kris De Volder + */ +public class ReactorUtils { + + private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(45); // reflects default timeout of Mono.block in reactor 2.x. + public static boolean DUMP_STACK_ON_TIMEOUT = false; + + /** + * Convert a {@link CancelationToken} into a Mono that raises + * an {@link OperationCanceledException} when the token is canceled. + */ + public static Mono toMono(CancelationToken cancelToken) { +// return Mono.delay(Duration.ofSeconds(1)) +// .then((ping) -> +// cancelToken.isCanceled() +// ? Mono.error(new OperationCanceledException()) +// : Mono.empty() +// ) +// .repeatWhenEmpty((x) -> x); + + return Mono.empty(); + } + + /** + * Similar to Mono.get but logs a more traceable version of the exception to Eclipse's error + * log before 'rethrowing' it. + *

+ * This is useful because the actual exception is pretty hard to trace. It doesn't even 'point' + * to the line where 'get' was called. + */ + public static T get(Mono mono) throws Exception { + try { + return mono.block(DEFAULT_TIMEOUT); + } catch (Exception e) { + dumpStacks(); + throw new IOException(e); + } + } + + /** + * Similar to Mono.get but logs a more traceable version of the exception to Eclipse's error + * log before 'rethrowing' it. + *

+ * This is useful because the actual exception is pretty hard to trace. It doesn't even 'point' + * to the line where 'get' was called. + */ + public static T get(Duration timeout, CancelationToken cancelationToken, Mono mono) throws Exception { + try { + return Mono.first(mono, + toMono(cancelationToken)) + .otherwise(errorFilter(cancelationToken)) + .block(timeout); + } catch (Exception e) { + dumpStacks(); + throw new IOException(e); + } + } + + + public static List get(Duration t, Mono> m) throws IOException { + try { + return m.block(t); + } catch (Exception e) { + dumpStacks(); + throw new IOException(e); + } + } + + private static void dumpStacks() { + if (DUMP_STACK_ON_TIMEOUT) { + System.out.println(getStackDumps().toString()); + } + } + + /** + * A 'filter' to use as a Mono.otherwise hanlder. It transforms any exception into {@link OperationCanceledException} + * when cancelationToken has been canceled. + */ + private static Function> errorFilter(CancelationToken cancelationToken) { +// return (Throwable e) -> cancelationToken.isCanceled()?Mono.error(new OperationCanceledException()):Mono.error(e); + return (Throwable e) -> Mono.error(e); + + } + + /** + * Deprecated because this is really the same as Mono.justOrEmpty, so use that instead. + */ + @Deprecated + public static Mono just(T it) { + return it == null ? Mono.empty() : Mono.just(it); + } + + /** + * @return A function that can be passed to Mono.otherwise to convert a specific exception type into + * Mono.empty(). + */ + public static Function> suppressException(Class exceptionType) { + return (Throwable caught) -> { + if (exceptionType.isAssignableFrom(caught.getClass())) { + return Mono.empty(); + } else { + return Mono.error(caught); + } + }; + } + + /** + * Build a Mono that executes a given number of Mono one after the + * other. + */ + @SafeVarargs + public static Mono sequence(Mono... tasks) { + Mono seq = Mono.empty(); + for (Mono t : tasks) { + seq = seq.then(t); + } + return seq; + } + + /** + * Execute a bunch of mono in parallel. All monos are executed to completion (rather than canceled early + * when one of them fails) + *

+ * When at least one operation has failed then, upon completion or failure of the last Mono we guarantee that at least + * one of the exceptions is propagated. + */ + public static Mono safeMerge(Flux> operations, int concurrency) { + AtomicReference failure = new AtomicReference<>(null); + return Flux.merge( + operations + .map((Mono op) -> { + return op.otherwise((e) -> { + failure.compareAndSet(null, e); + return Mono.empty(); + }); + }), + concurrency //limit concurrency otherwise troubles (flooding/choking request broker?) + ) + .then(() -> { + Throwable error = failure.get(); + if (error!=null) { + return Mono.error(error); + } else { + return Mono.empty(); + } + }); + } + + /** + * Attach a timestamp to each element in a Stream + */ + public static Flux> timestamp(Flux stream) { + return stream.map((e) -> Tuples.of(e, System.currentTimeMillis())); + } + + /** + * Sorts the elements in a flux in a moving time window. I.e. this assumes element order may be + * scrambled but the scrambling has a certain 'time localilty' to it. So we only need to consider + * sorting of elements that arrive 'close to eachother'. + *

+ * WARNING: The returned flux is intended for a single subscriber. It only maintains a + * single buffer for sorting stream elements. This buffer is consumed when elements + * are released to any subscriber. Therefore if one subscriber received a element it is gone + * from the buffer and will not be delivered to the other subscribers. + * + * @param stream The stream to be sorted + * @param comparator Compare function to sort with + * @param bufferTime The 'window' of time beyond which we don't need to compare elements. + */ + public static Flux sort(Flux stream, Comparator comparator, Duration bufferTime) { + + class SorterAccumulator { + + final PriorityQueue> holdingPen = new PriorityQueue<>((Tuple2 o1, Tuple2 o2) -> { + return comparator.compare(o1.getT1(), o2.getT1()); + }); + + final Flux released = Flux.fromIterable(() -> new Iterator() { + @Override + public boolean hasNext() { + Tuple2 nxt; + synchronized (holdingPen) { + nxt = holdingPen.peek(); + } + return nxt!=null && isOldEnough(nxt); + } + + private boolean isOldEnough(Tuple2 nxt) { + long age = System.currentTimeMillis() - nxt.getT2(); + return age > bufferTime.toMillis(); + } + + @Override + public T next() { + synchronized (holdingPen) { + return holdingPen.remove().getT1(); + } + } + }); + + public SorterAccumulator next(Flux> window) { + window.subscribe((e) -> { + synchronized (holdingPen) { + holdingPen.add(e); + } + }); + return this; + } + + public Flux getReleased() { + return released; + } + + public Publisher drain() { + return Flux.fromIterable(holdingPen) + .map(Tuple2::getT1); + } + } + + SorterAccumulator sorter = new SorterAccumulator(); + return timestamp(stream) + .window(bufferTime) + .scan(sorter, SorterAccumulator::next) + .concatMap(SorterAccumulator::getReleased) + .concatWith(sorter.drain()); + } + + protected static StringBuffer getStackDumps() { + StringBuffer sb = new StringBuffer(); + Map traces = Thread.getAllStackTraces(); + for (Map.Entry entry : traces.entrySet()) { + sb.append(entry.getKey().toString()); + sb.append("\n"); + for (StackTraceElement element : entry.getValue()) { + sb.append(" "); + sb.append(element.toString()); + sb.append("\n"); + } + sb.append("\n"); + } + return sb; + } + + + /** + * Connect a mono to a CompletableFuture so that the result of the mono + * can be retrieved from the {@link CompletableFuture} by calling it's 'get' + * method. + */ + public static void completeWith(CompletableFuture future, Mono mono) { + mono.doOnNext((T v) -> { + future.complete(v); + }) + .doOnError((Throwable e) -> { + future.completeExceptionally(e); + }) + .subscribeOn(Schedulers.elastic()) + .subscribe(); + } +} diff --git a/vscode-extensions/commons/commons-cf/src/test/java/CFClientTest.java b/vscode-extensions/commons/commons-cf/src/test/java/CFClientTest.java new file mode 100644 index 000000000..d21aaa8c7 --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/test/java/CFClientTest.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack; +import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFClientParams; +import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFClientParamsFactory; +import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.ClientRequests; +import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2; + +public class CFClientTest { + + @Ignore @Test public void testGetBuildpacksFromCliParams() throws Exception { + + CFClientParams params = CFClientParamsFactory.INSTANCE.getParams().get(0); + assertNotNull(params); + + ClientRequests requests = DefaultCloudFoundryClientFactoryV2.INSTANCE.getClient(params); + + assertNotNull(requests); + + List buildPacks = requests.getBuildpacks(); + assertTrue(!buildPacks.isEmpty()); + + } + +} diff --git a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java index 7135a9c0d..db7cbbe1d 100644 --- a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java @@ -76,4 +76,8 @@ public class ExceptionUtil { return new ExecutionException(cause); } + public static Object exception(String string, Throwable error) { + return exception(error); + } + } diff --git a/vscode-extensions/commons/pom.xml b/vscode-extensions/commons/pom.xml index ea520ebbf..d0aa2f5b4 100644 --- a/vscode-extensions/commons/pom.xml +++ b/vscode-extensions/commons/pom.xml @@ -16,7 +16,7 @@ commons-util commons-java java-properties - + commons-cf commons-maven @@ -45,6 +45,11 @@ true + + spring-releases + Spring Releases + http://repo.spring.io/release + From 5da35b7499dd7f0f7bb7c70bd025900612db3a3c Mon Sep 17 00:00:00 2001 From: nsingh Date: Thu, 5 Jan 2017 11:02:44 -0800 Subject: [PATCH 2/5] Renamed test package --- .../ide/vscode/commons/cloudfoundry/client}/CFClientTest.java | 2 ++ 1 file changed, 2 insertions(+) rename vscode-extensions/commons/commons-cf/src/test/java/{ => org/springframework/ide/vscode/commons/cloudfoundry/client}/CFClientTest.java (95%) diff --git a/vscode-extensions/commons/commons-cf/src/test/java/CFClientTest.java b/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java similarity index 95% rename from vscode-extensions/commons/commons-cf/src/test/java/CFClientTest.java rename to vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java index d21aaa8c7..a6b96b6b8 100644 --- a/vscode-extensions/commons/commons-cf/src/test/java/CFClientTest.java +++ b/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java @@ -8,6 +8,8 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client; + import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; From c5f0457ecc1dfaeabee0b850de2a736160b8b602 Mon Sep 17 00:00:00 2001 From: nsingh Date: Thu, 5 Jan 2017 11:28:07 -0800 Subject: [PATCH 3/5] Fixed exception handling in util when a message is present --- .../ide/vscode/commons/util/ExceptionUtil.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java index db7cbbe1d..ce0b380d1 100644 --- a/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java +++ b/vscode-extensions/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExceptionUtil.java @@ -76,8 +76,13 @@ public class ExceptionUtil { return new ExecutionException(cause); } - public static Object exception(String string, Throwable error) { - return exception(error); + public static Object exception(String message, Throwable error) { + if (message != null) { + // Wrap only if there is an additional message + return new ExecutionException(message, error); + } else { + return exception(error); + } } } From 2586dc419ffbb57dd09c41a38b40e049ffd9138f Mon Sep 17 00:00:00 2001 From: nsingh Date: Thu, 5 Jan 2017 12:42:23 -0800 Subject: [PATCH 4/5] Renamed cf target package --- .../client/{params => target}/CFClientParams.java | 4 ++-- .../client/{params => target}/CFClientParamsFactory.java | 6 +++++- .../client/{params => target}/CFCredentials.java | 2 +- .../client/{params => target}/CfCliParamsProvider.java | 2 +- .../cloudfoundry/client/v2/CloudFoundryClientCache.java | 2 +- .../cloudfoundry/client/v2/DefaultClientRequestsV2.java | 2 +- .../client/v2/DefaultCloudFoundryClientFactoryV2.java | 2 +- .../vscode/commons/cloudfoundry/client/CFClientTest.java | 4 ++-- 8 files changed, 14 insertions(+), 10 deletions(-) rename vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/{params => target}/CFClientParams.java (98%) rename vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/{params => target}/CFClientParamsFactory.java (94%) rename vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/{params => target}/CFCredentials.java (99%) rename vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/{params => target}/CfCliParamsProvider.java (99%) diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParams.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFClientParams.java similarity index 98% rename from vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParams.java rename to vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFClientParams.java index f17674c08..aaeb639bd 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParams.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFClientParams.java @@ -8,13 +8,13 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.commons.cloudfoundry.client.params; +package org.springframework.ide.vscode.commons.cloudfoundry.client.target; import java.net.URI; import java.util.logging.Level; import java.util.logging.Logger; -import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFCredentials.CFCredentialType; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFCredentials.CFCredentialType; import org.springframework.ide.vscode.commons.util.Assert; /** diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParamsFactory.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFClientParamsFactory.java similarity index 94% rename from vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParamsFactory.java rename to vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFClientParamsFactory.java index b48092c08..ecbecbd9e 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFClientParamsFactory.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFClientParamsFactory.java @@ -8,13 +8,17 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.commons.cloudfoundry.client.params; +package org.springframework.ide.vscode.commons.cloudfoundry.client.target; import java.util.ArrayList; import java.util.List; import javax.inject.Provider; +/** + * Resolves Cloud Foundry client parameters from registered params providers + * + */ public class CFClientParamsFactory { public static final CFClientParamsFactory INSTANCE = new CFClientParamsFactory(); diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFCredentials.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFCredentials.java similarity index 99% rename from vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFCredentials.java rename to vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFCredentials.java index 670c1280d..7ed2be42c 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CFCredentials.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFCredentials.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.commons.cloudfoundry.client.params; +package org.springframework.ide.vscode.commons.cloudfoundry.client.target; import org.springframework.ide.vscode.commons.cloudfoundry.client.LoginMethod; import org.springframework.ide.vscode.commons.util.Assert; diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CfCliParamsProvider.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CfCliParamsProvider.java similarity index 99% rename from vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CfCliParamsProvider.java rename to vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CfCliParamsProvider.java index 0c8335cc5..c56a8b36a 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/params/CfCliParamsProvider.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CfCliParamsProvider.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.commons.cloudfoundry.client.params; +package org.springframework.ide.vscode.commons.cloudfoundry.client.target; import java.io.File; import java.io.IOException; diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java index 1763217ce..e12b171b8 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientCache.java @@ -26,7 +26,7 @@ import org.cloudfoundry.reactor.tokenprovider.OneTimePasscodeTokenProvider; import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider; import org.cloudfoundry.reactor.tokenprovider.RefreshTokenGrantTokenProvider; import org.cloudfoundry.reactor.uaa.ReactorUaaClient; -import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFCredentials; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFCredentials; /** * TODO: Remove this class when the 'thread leak bug' in V2 client is fixed. diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java index 53f082353..57c7474db 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultClientRequestsV2.java @@ -78,7 +78,7 @@ import org.springframework.ide.vscode.commons.cloudfoundry.client.CFSpace; import org.springframework.ide.vscode.commons.cloudfoundry.client.CFStack; import org.springframework.ide.vscode.commons.cloudfoundry.client.SshClientSupport; import org.springframework.ide.vscode.commons.cloudfoundry.client.SshHost; -import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFClientParams; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams; import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CancelationTokens.CancelationToken; import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CloudFoundryClientCache.CFClientProvider; import org.springframework.ide.vscode.commons.util.ExceptionUtil; diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java index 66c737cd1..06db48663 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java @@ -10,7 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; -import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFClientParams; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams; public class DefaultCloudFoundryClientFactoryV2 { diff --git a/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java b/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java index a6b96b6b8..d6351d66f 100644 --- a/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java +++ b/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java @@ -18,8 +18,8 @@ import java.util.List; import org.junit.Ignore; import org.junit.Test; import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack; -import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFClientParams; -import org.springframework.ide.vscode.commons.cloudfoundry.client.params.CFClientParamsFactory; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParamsFactory; import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.ClientRequests; import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2; From 46eb0466e2c1a447edc286ce8644c238d796e746 Mon Sep 17 00:00:00 2001 From: nsingh Date: Sat, 7 Jan 2017 11:59:17 -0800 Subject: [PATCH 5/5] Add dynamic CF buildpacks and services completion values --- vscode-extensions/commons/commons-cf/pom.xml | 15 ++-- .../client/target/CFClientParams.java | 8 +++ .../client/v2/CloudFoundryClientFactory.java | 19 +++++ .../DefaultCloudFoundryClientFactoryV2.java | 10 ++- .../cloudfoundry/client/v2/ReactorUtils.java | 24 ++++--- .../cloudfoundry/client/CFClientTest.java | 21 +++--- .../languageserver/testharness/Editor.java | 16 +++++ vscode-extensions/commons/pom.xml | 5 +- .../vscode-manifest-yaml/pom.xml | 6 ++ .../ManifestYamlCFBuildpacksProvider.java | 69 +++++++++++++++++++ .../yaml/ManifestYamlCFServicesProvider.java | 67 ++++++++++++++++++ .../yaml/ManifestYamlLanguageServer.java | 62 ++++++++++++++++- .../manifest/yaml/ManifestYmlSchema.java | 16 +++-- .../yaml/ManifestYamlEditorCFTest.java | 63 +++++++++++++++++ .../manifest/yaml/ManifestYmlSchemaTest.java | 2 +- 15 files changed, 358 insertions(+), 45 deletions(-) create mode 100644 vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientFactory.java create mode 100644 vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFBuildpacksProvider.java create mode 100644 vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFServicesProvider.java create mode 100644 vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorCFTest.java diff --git a/vscode-extensions/commons/commons-cf/pom.xml b/vscode-extensions/commons/commons-cf/pom.xml index 677e43a09..a80147c9d 100644 --- a/vscode-extensions/commons/commons-cf/pom.xml +++ b/vscode-extensions/commons/commons-cf/pom.xml @@ -11,13 +11,6 @@ 0.0.1-SNAPSHOT ../pom.xml - - - 2.1.0.RELEASE - 3.0.4.RELEASE - 0.6.0.RELEASE - - @@ -28,22 +21,22 @@ org.cloudfoundry cloudfoundry-client-reactor - ${cloudfoundry.client.version} + ${cloudfoundry-client-version} org.cloudfoundry cloudfoundry-operations - ${cloudfoundry.client.version} + ${cloudfoundry-client-version} io.projectreactor reactor-core - ${client.reactor.version} + ${reactor-version} io.projectreactor.ipc reactor-netty - ${reactor.netty} + ${reactor-netty} \ No newline at end of file diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFClientParams.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFClientParams.java index aaeb639bd..479f7b65b 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFClientParams.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/target/CFClientParams.java @@ -148,4 +148,12 @@ public class CFClientParams { return true; } + @Override + public String toString() { + return "CFClientParams [apiUrl=" + apiUrl + ", username=" + username + ", credentials=" + credentials + + ", skipSslValidation=" + skipSslValidation + ", orgName=" + orgName + ", spaceName=" + spaceName + + "]"; + } + + } diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientFactory.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientFactory.java new file mode 100644 index 000000000..f54a993ae --- /dev/null +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/CloudFoundryClientFactory.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams; + +public interface CloudFoundryClientFactory { + + ClientRequests getClient(CFClientParams params) throws Exception; + +} \ No newline at end of file diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java index 06db48663..312f69f0a 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/DefaultCloudFoundryClientFactoryV2.java @@ -12,9 +12,9 @@ package org.springframework.ide.vscode.commons.cloudfoundry.client.v2; import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams; -public class DefaultCloudFoundryClientFactoryV2 { +public class DefaultCloudFoundryClientFactoryV2 implements CloudFoundryClientFactory { - public static final DefaultCloudFoundryClientFactoryV2 INSTANCE = new DefaultCloudFoundryClientFactoryV2(); + public static final CloudFoundryClientFactory INSTANCE = new DefaultCloudFoundryClientFactoryV2(); /** * Use 'INSTANCE' constant instead. This class is a singleton. @@ -23,7 +23,11 @@ public class DefaultCloudFoundryClientFactoryV2 { private CloudFoundryClientCache cache = new CloudFoundryClientCache(); - public ClientRequests getClient(CFClientParams params) { + /* (non-Javadoc) + * @see org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CloudFoundryClientFactory#getClient(org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams) + */ + @Override + public ClientRequests getClient(CFClientParams params) throws Exception { return new DefaultClientRequestsV2(cache, params); } } diff --git a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ReactorUtils.java b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ReactorUtils.java index 53037c797..aaec46eca 100644 --- a/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ReactorUtils.java +++ b/vscode-extensions/commons/commons-cf/src/main/java/org/springframework/ide/vscode/commons/cloudfoundry/client/v2/ReactorUtils.java @@ -38,11 +38,12 @@ public class ReactorUtils { private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(45); // reflects default timeout of Mono.block in reactor 2.x. public static boolean DUMP_STACK_ON_TIMEOUT = false; - /** - * Convert a {@link CancelationToken} into a Mono that raises - * an {@link OperationCanceledException} when the token is canceled. - */ - public static Mono toMono(CancelationToken cancelToken) { + // TODO: uncommented when cancellation is handled in vscode +// /** +// * Convert a {@link CancelationToken} into a Mono that raises +// * an {@link OperationCanceledException} when the token is canceled. +// */ +// public static Mono toMono(CancelationToken cancelToken) { // return Mono.delay(Duration.ofSeconds(1)) // .then((ping) -> // cancelToken.isCanceled() @@ -50,9 +51,7 @@ public class ReactorUtils { // : Mono.empty() // ) // .repeatWhenEmpty((x) -> x); - - return Mono.empty(); - } +// } /** * Similar to Mono.get but logs a more traceable version of the exception to Eclipse's error @@ -79,9 +78,12 @@ public class ReactorUtils { */ public static T get(Duration timeout, CancelationToken cancelationToken, Mono mono) throws Exception { try { - return Mono.first(mono, - toMono(cancelationToken)) - .otherwise(errorFilter(cancelationToken)) + return mono + // TODO: uncomment when cancellation properly supported in vscode +// Mono +// .first(mono, +// toMono(cancelationToken)) +// .otherwise(errorFilter(cancelationToken)) .block(timeout); } catch (Exception e) { dumpStacks(); diff --git a/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java b/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java index d6351d66f..7e2982a7d 100644 --- a/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java +++ b/vscode-extensions/commons/commons-cf/src/test/java/org/springframework/ide/vscode/commons/cloudfoundry/client/CFClientTest.java @@ -10,33 +10,32 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.cloudfoundry.client; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.util.List; import org.junit.Ignore; import org.junit.Test; -import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack; -import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParams; import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParamsFactory; -import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.ClientRequests; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTarget; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTargets; +import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CloudFoundryClientFactory; import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2; public class CFClientTest { - @Ignore @Test public void testGetBuildpacksFromCliParams() throws Exception { - CFClientParams params = CFClientParamsFactory.INSTANCE.getParams().get(0); - assertNotNull(params); + + @Ignore @Test public void testGetBuildpacksFromCliParamsTarget() throws Exception { - ClientRequests requests = DefaultCloudFoundryClientFactoryV2.INSTANCE.getClient(params); + CFClientParamsFactory paramsFactory = CFClientParamsFactory.INSTANCE; + CloudFoundryClientFactory clientFactory = DefaultCloudFoundryClientFactoryV2.INSTANCE; - assertNotNull(requests); + CFClientTargets targets = new CFClientTargets(paramsFactory, clientFactory); + CFClientTarget target = targets.getTargets().get(0); - List buildPacks = requests.getBuildpacks(); + List buildPacks = target.getBuildpacks(); assertTrue(!buildPacks.isEmpty()); - } } diff --git a/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index 79cce2554..0221ba872 100644 --- a/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/vscode-extensions/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -238,6 +238,22 @@ public class Editor { assertEquals(expect.toString(), actual.toString()); } + public void assertContainsCompletions(String... expectTextAfter) throws Exception { + StringBuilder actual = new StringBuilder(); + + for (CompletionItem completion : getCompletions()) { + Editor editor = this.clone(); + editor.apply(completion); + actual.append(editor.getText()); + actual.append("\n-------------------\n"); + } + String actualText = actual.toString(); + + for (String after : expectTextAfter) { + assertContains(after, actualText); + } + } + public void apply(CompletionItem completion) throws Exception { TextEdit edit = completion.getTextEdit(); String docText = document.getText(); diff --git a/vscode-extensions/commons/pom.xml b/vscode-extensions/commons/pom.xml index d0aa2f5b4..78b1128a2 100644 --- a/vscode-extensions/commons/pom.xml +++ b/vscode-extensions/commons/pom.xml @@ -62,7 +62,10 @@ 2.5.0 2.10 0.1.0-SNAPSHOT - 3.0.2.RELEASE + + 3.0.4.RELEASE + 0.6.0.RELEASE + 2.1.0.RELEASE diff --git a/vscode-extensions/vscode-manifest-yaml/pom.xml b/vscode-extensions/vscode-manifest-yaml/pom.xml index 741d863fc..46b14d30c 100644 --- a/vscode-extensions/vscode-manifest-yaml/pom.xml +++ b/vscode-extensions/vscode-manifest-yaml/pom.xml @@ -42,6 +42,12 @@ commons-yaml ${project.version} + + + org.springframework.ide.vscode + commons-cf + ${project.version} + org.springframework.ide.vscode diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFBuildpacksProvider.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFBuildpacksProvider.java new file mode 100644 index 000000000..21ebd3e3c --- /dev/null +++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFBuildpacksProvider.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.manifest.yaml; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.inject.Provider; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFBuildpack; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTarget; +import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; + +public class ManifestYamlCFBuildpacksProvider implements Provider> { + + private final List targets; + private static final Logger logger = Logger.getLogger(ManifestYamlCFBuildpacksProvider.class.getName()); + + + public ManifestYamlCFBuildpacksProvider(List targets) { + this.targets = targets; + } + + + @Override + public Collection get() { + List hints = new ArrayList<>(); + + if (targets != null) { + for (CFClientTarget cfClientTarget : targets) { + + List buildpacks; + try { + buildpacks = cfClientTarget.getBuildpacks(); + if (buildpacks != null) { + for (CFBuildpack buildpack : buildpacks) { + String name = buildpack.getName(); + String label = getBuildpackLabel(cfClientTarget, buildpack); + YValueHint hint = new BasicYValueHint(name, label); + if (!hints.contains(hint)) { + hints.add(hint); + } + } + } + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + } + } + return hints; + } + + protected String getBuildpackLabel(CFClientTarget target, CFBuildpack buildpack) { + return buildpack.getName() + " (" + target.getName() + ")"; + } + +} diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFServicesProvider.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFServicesProvider.java new file mode 100644 index 000000000..b80b1377b --- /dev/null +++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlCFServicesProvider.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.manifest.yaml; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.inject.Provider; + +import org.springframework.ide.vscode.commons.cloudfoundry.client.CFServiceInstance; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTarget; +import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint; +import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; + +public class ManifestYamlCFServicesProvider implements Provider> { + + private final List targets; + + private static final Logger logger = Logger.getLogger(ManifestYamlCFServicesProvider.class.getName()); + + public ManifestYamlCFServicesProvider(List targets) { + this.targets = targets; + } + + @Override + public Collection get() { + List hints = new ArrayList<>(); + + if (targets != null) { + for (CFClientTarget cfClientTarget : targets) { + + try { + List services = cfClientTarget.getClientRequests().getServices(); + if (services != null) { + for (CFServiceInstance service : services) { + String name = service.getName(); + String label = getServiceLabel(cfClientTarget, service); + YValueHint hint = new BasicYValueHint(name, label); + if (!hints.contains(hint)) { + hints.add(hint); + } + } + } + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + } + } + return hints; + } + + private String getServiceLabel(CFClientTarget cfClientTarget, CFServiceInstance service) { + return service.getName() + " - " + service.getPlan() + " (" + cfClientTarget.getParams().getOrgName() + " - " + + cfClientTarget.getParams().getSpaceName() + ")"; + } +} diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java index 6e2ac5259..c52d606cc 100644 --- a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java +++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlLanguageServer.java @@ -1,12 +1,20 @@ package org.springframework.ide.vscode.manifest.yaml; import java.util.Collection; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.inject.Provider; import org.eclipse.lsp4j.CompletionOptions; import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.TextDocumentSyncKind; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientParamsFactory; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTarget; +import org.springframework.ide.vscode.commons.cloudfoundry.client.target.CFClientTargets; +import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.CloudFoundryClientFactory; +import org.springframework.ide.vscode.commons.cloudfoundry.client.v2.DefaultCloudFoundryClientFactoryV2; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngine; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; @@ -32,16 +40,25 @@ import com.google.common.collect.ImmutableList; public class ManifestYamlLanguageServer extends SimpleLanguageServer { - private static final Provider> NO_BUILDPACKS = () -> ImmutableList.of(); + private static final Provider> NO_PROVIDER = () -> ImmutableList.of(); + private static Logger logger = Logger.getLogger(ManifestYamlLanguageServer.class.getName()); private Yaml yaml = new Yaml(); - private YamlSchema schema = new ManifestYmlSchema(NO_BUILDPACKS); + private YamlSchema schema; + private CFClientTargets cfClientTargets; public ManifestYamlLanguageServer() { SimpleTextDocumentService documents = getTextDocumentService(); YamlASTProvider parser = new YamlParser(yaml); + + CFClientTargets cfTargets = getCFTargets(); + + Provider> buildPacksProvider = getBuildpacksProvider(cfTargets); + Provider> servicesProvider = getServicesProvider(cfTargets); + + schema = new ManifestYmlSchema(buildPacksProvider, servicesProvider); YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT; YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema); @@ -72,7 +89,48 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer { documents.onCompletionResolve(completionEngine::resolveCompletion); documents.onHover(hoverEngine ::getHover); } + + private CFClientTargets getCFTargets() { + if (cfClientTargets == null) { + CFClientParamsFactory paramsFactory = CFClientParamsFactory.INSTANCE; + CloudFoundryClientFactory clientFactory = DefaultCloudFoundryClientFactoryV2.INSTANCE; + cfClientTargets = new CFClientTargets(paramsFactory, clientFactory); + } + return cfClientTargets; + } + private Provider> getBuildpacksProvider(CFClientTargets targets) { + + try { + if (targets != null) { + List cfTargets = targets.getTargets(); + if (cfTargets != null && !cfTargets.isEmpty()) {; + return new ManifestYamlCFBuildpacksProvider(cfTargets); + } + } + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + + return NO_PROVIDER; + } + + private Provider> getServicesProvider(CFClientTargets targets) { + + try { + if (targets != null) { + List cfTargets = targets.getTargets(); + if (cfTargets != null && !cfTargets.isEmpty()) { + return new ManifestYamlCFServicesProvider(cfTargets); + } + } + + } catch (Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + + return null; + } @Override protected ServerCapabilities getServerCapabilities() { diff --git a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java index 8840d1873..778500476 100644 --- a/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java +++ b/vscode-extensions/vscode-manifest-yaml/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java @@ -19,6 +19,7 @@ import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.yaml.schema.YType; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory; +import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPropertyImpl; @@ -41,7 +42,7 @@ public class ManifestYmlSchema implements YamlSchema { "name", "host", "hosts" ); - public ManifestYmlSchema(Provider> buildpackProvider) { + public ManifestYmlSchema(Provider> buildpackProvider, Provider> servicesProvider) { this.buildpackProvider = buildpackProvider; YTypeFactory f = new YTypeFactory(); TYPE_UTIL = f.TYPE_UTIL; @@ -51,15 +52,20 @@ public class ManifestYmlSchema implements YamlSchema { YBeanType application = f.ybean("Application"); YAtomicType t_path = f.yatomic("Path"); - - YAtomicType t_buildpack = f.yatomic("Buildpack"); + YAtomicType t_buildpack = f.yatomic("Buildpack"); t_buildpack.addHintProvider(this.buildpackProvider); + + YType t_service_string = f.yatomic("String"); + if (servicesProvider != null && t_service_string instanceof AbstractType) { + ((AbstractType)t_service_string).addHintProvider(servicesProvider); + } + YType t_services = f.yseq(t_service_string); YAtomicType t_boolean = f.yenum("boolean", "true", "false"); YType t_string = f.yatomic("String"); YType t_strings = f.yseq(t_string); - + YAtomicType t_memory = f.yatomic("Memory"); t_memory.addHints("256M", "512M", "1024M"); t_memory.parseWith(ManifestYmlValueParsers.MEMORY); @@ -94,7 +100,7 @@ public class ManifestYmlSchema implements YamlSchema { f.yprop("no-route", t_boolean), f.yprop("path", t_path), f.yprop("random-route", t_boolean), - f.yprop("services", t_strings), + f.yprop("services", t_services), f.yprop("stack", t_string), f.yprop("timeout", t_pos_integer), f.yprop("health-check-type", t_health_check_type) diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorCFTest.java b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorCFTest.java new file mode 100644 index 000000000..59e374587 --- /dev/null +++ b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorCFTest.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.manifest.yaml; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.ide.vscode.languageserver.testharness.Editor; +import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; + +public class ManifestYamlEditorCFTest { + LanguageServerHarness harness; + + @Before + public void setup() throws Exception { + harness = new LanguageServerHarness(ManifestYamlLanguageServer::new); + harness.intialize(null); + } + + /* + * Optional test that is here only to be run in certain conditions. Tests if + * buildpack completion values are actually fetched from PWS if the test env + * also has a CLI that is alread connected to PWS. Enable only if underlying + * conditions for CF connection from vscode are present. For example, CLI is + * installed. + */ + @Ignore + @Test + public void optionalDynamicBuildpacksPWSUsingCliParams() throws Exception { + // No special test harness setup to use CLI. It is a "default" CF client params + // provider in the CF vscode framework. + // Just have to make sure the test env has a CLI that is connected to + // PWS + assertContainsCompletions("buildpack: <*>", "buildpack: java_buildpack<*>"); + } + + @Ignore + @Test + public void optionalDynamicServicesPWSUsingCliParams() throws Exception { + // No special test harness setup to use CLI. It is a "default" CF client params + // provider in the CF vscode framework. + // Just have to make sure the test env has a CLI that is connected to + // PWS + assertContainsCompletions( "services:\n"+ + " - <*>", "sql"); + } + + ////////////////////////////////////////////////////////////////////////////// + + private void assertContainsCompletions(String textBefore, String... textAfter) throws Exception { + Editor editor = harness.newEditor(textBefore); + editor.assertContainsCompletions(textAfter); + } + +} diff --git a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java index 02a5f4727..2c01cfa78 100644 --- a/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java +++ b/vscode-extensions/vscode-manifest-yaml/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchemaTest.java @@ -82,7 +82,7 @@ public class ManifestYmlSchemaTest { "timeout" }; - ManifestYmlSchema schema = new ManifestYmlSchema(null); + ManifestYmlSchema schema = new ManifestYmlSchema(null, null); @Test public void toplevelProperties() throws Exception {