diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java index e3fbbd386..bd870a0c4 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java @@ -10,31 +10,43 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh; +import java.util.Map; import java.util.UUID; +import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; -import org.springframework.ide.vscode.commons.util.ValueParser; import org.springframework.ide.vscode.commons.util.ValueParsers; 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.YContextSensitive; import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPropertyImpl; import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil; +import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; +import com.google.common.collect.ImmutableSet; + /** * @author Kris De Volder */ public class BoshDeploymentManifestSchema implements YamlSchema { - private final AbstractType TOPLEVEL_TYPE; + private final YBeanType V2_TOPLEVEL_TYPE; + private final YBeanType V1_TOPLEVEL_TYPE; + private final YContextSensitive TOPLEVEL_TYPE; private final YTypeUtil TYPE_UTIL; + + private static final ImmutableSet DEPRECATED_V1_PROPS = ImmutableSet.of("resource_pools", "networks", "compilation", "jobs", "disk_pools", "cloud_provider"); + private static final ImmutableSet SHARED_V1_V2_PROPS = ImmutableSet.of("name", "director_uuid", "releases", "update", "properties"); + //Note: 'director_uuid' is also deprecated. But its treated separately since it is deprecated and ignored by V2 client no matter what (i.e. deprecated in both schemas) public final YTypeFactory f = new YTypeFactory() - .enableTieredProposals(false); + .enableTieredProposals(false) + .suggestDeprecatedProperties(false); public final YType t_string = f.yatomic("String"); public final YType t_ne_string = f.yatomic("String") .parseWith(ValueParsers.NE_STRING); @@ -56,9 +68,34 @@ public class BoshDeploymentManifestSchema implements YamlSchema { public BoshDeploymentManifestSchema() { TYPE_UTIL = f.TYPE_UTIL; - TOPLEVEL_TYPE = f.ybean("BoshDeploymentManifest"); - addProp(TOPLEVEL_TYPE, "name", t_ne_string).isPrimary(true); - addProp(TOPLEVEL_TYPE, "director_uuid", t_uuid).isDeprecated( + V2_TOPLEVEL_TYPE = createV2Schema(); + V1_TOPLEVEL_TYPE = createV1Schema(V2_TOPLEVEL_TYPE); + + TOPLEVEL_TYPE = f.contextAware("DeploymenManifestV1orV2", (dc) -> { + boolean looksLikeV1 = dc.getDefinedProperties().stream().anyMatch(DEPRECATED_V1_PROPS::contains); + return looksLikeV1 ? V1_TOPLEVEL_TYPE : V2_TOPLEVEL_TYPE; + }); + } + + private YBeanType createV1Schema(AbstractType v2Schema) { + YBeanType v1Schema = f.ybean("DeploymentManifestV1"); + Map v2properties = v2Schema.getPropertiesMap(); + ImmutableSet v1Props = ImmutableSet.builder() + .addAll(DEPRECATED_V1_PROPS) + .addAll(SHARED_V1_V2_PROPS) + .build(); + for (String name : v1Props) { + YTypedProperty prop = v2properties.get(name); + Assert.isNotNull(prop); + v1Schema.addProperty(prop); + } + return v1Schema; + } + + private YBeanType createV2Schema() { + YBeanType v2Schema = f.ybean("BoshDeploymentManifest"); + addProp(v2Schema, "name", t_ne_string).isPrimary(true); + addProp(v2Schema, "director_uuid", t_uuid).isDeprecated( "bosh v2 CLI no longer checks or requires director_uuid in the deployment manifest. " + "To achieve similar safety make sure to give unique deployment names across environments." ); @@ -100,7 +137,7 @@ public class BoshDeploymentManifestSchema implements YamlSchema { YBeanType t_release = f.ybean("Release"); addProp(t_release, "name", t_ne_string).isPrimary(true); addProp(t_release, "version", t_version).isRequired(true); - addProp(TOPLEVEL_TYPE, "releases", f.yseq(t_release)).isRequired(true); + addProp(v2Schema, "releases", f.yseq(t_release)).isRequired(true); YBeanType t_stemcell = f.ybean("Stemcell"); addProp(t_stemcell, "alias", t_ne_string).isRequired(true); @@ -108,7 +145,7 @@ public class BoshDeploymentManifestSchema implements YamlSchema { addProp(t_stemcell, "name", t_ne_string); addProp(t_stemcell, "os", t_ne_string); t_stemcell.requireOneOf("name", "os"); - addProp(TOPLEVEL_TYPE, "stemcells", f.yseq(t_stemcell)).isRequired(true); + addProp(v2Schema, "stemcells", f.yseq(t_stemcell)).isRequired(true); YBeanType t_update = f.ybean("Update"); addProp(t_update, "canaries", t_strictly_pos_integer).isRequired(true); @@ -116,7 +153,7 @@ public class BoshDeploymentManifestSchema implements YamlSchema { addProp(t_update, "canary_watch_time", t_integer_or_range).isRequired(true); addProp(t_update, "update_watch_time", t_integer_or_range).isRequired(true); addProp(t_update, "serial", t_boolean); - addProp(TOPLEVEL_TYPE, "update", t_update).isRequired(true); + addProp(v2Schema, "update", t_update).isRequired(true); YBeanType t_job = f.ybean("Job"); addProp(t_job, "name", t_ne_string).isRequired(true); @@ -142,17 +179,21 @@ public class BoshDeploymentManifestSchema implements YamlSchema { addProp(t_instance_group, "properties", t_params).isDeprecated("Deprecated in favor of job level properties and links"); addProp(t_instance_group, "env", t_instance_group_env); - addProp(TOPLEVEL_TYPE, "instance_groups", f.yseq(t_instance_group)).isRequired(true); - addProp(TOPLEVEL_TYPE, "properties", t_params).isDeprecated("Deprecated in favor of job level properties and links"); + addProp(v2Schema, "instance_groups", f.yseq(t_instance_group)).isRequired(true); + addProp(v2Schema, "properties", t_params).isDeprecated("Deprecated in favor of job level properties and links"); YBeanType t_variable = f.ybean("Variable"); addProp(t_variable, "name", t_ne_string).isPrimary(true); addProp(t_variable, "type", f.yenum("VariableType", "certificate", "password", "rsa", "ssh")).isRequired(true); addProp(t_variable, "options", t_params); - addProp(TOPLEVEL_TYPE, "variables", f.yseq(t_variable)); + addProp(v2Schema, "variables", f.yseq(t_variable)); - addProp(TOPLEVEL_TYPE, "tags", t_params); + addProp(v2Schema, "tags", t_params); + for (String v1Prop : DEPRECATED_V1_PROPS) { + addProp(v2Schema, v1Prop, t_any).isDeprecated("Deprecated: '"+v1Prop+"' is a V1 schema property. Consider migrating your deployment manifest to V2"); + } + return v2Schema; } @Override diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java index 3f8979d29..84f1f8c6c 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java @@ -10,8 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.concourse; +import static org.springframework.ide.vscode.languageserver.testharness.Editor.PLAIN_COMPLETION; + import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.springframework.ide.vscode.bosh.BoshLanguageServer; import org.springframework.ide.vscode.commons.util.text.LanguageId; @@ -19,8 +20,6 @@ import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; import org.springframework.ide.vscode.languageserver.testharness.Editor; import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; -import static org.springframework.ide.vscode.languageserver.testharness.Editor.*; - public class BoshEditorTest { LanguageServerHarness harness; @@ -115,7 +114,7 @@ public class BoshEditorTest { editor.assertHoverContains("tags", "Specifies key value pairs to be sent to the CPI for VM tagging"); } - @Test public void toplevelV2PropertyCompletions() throws Exception { + @Test public void toplevelPropertyCompletions() throws Exception { Editor editor = harness.newEditor( "<*>" ); @@ -149,12 +148,13 @@ public class BoshEditorTest { "name: blah\n" + "variables:\n" + "- name: <*>" - , // ============ - "name: blah\n" + - "director_uuid: <*>" - , // ============ - "name: blah\n" + - "properties:\n <*>" +// Below completions are suppressed because they are deprecated +// , // ============ +// "name: blah\n" + +// "director_uuid: <*>" +// , // ============ +// "name: blah\n" + +// "properties:\n <*>" ); } @@ -312,11 +312,12 @@ public class BoshEditorTest { "- name: foo-group\n" + " vm_type: <*>" - , // ============= - "instance_groups:\n" + - "- name: foo-group\n" + - " properties:\n" + - " <*>" +// , // ============= +// Not suggested because its deprecated: +// "instance_groups:\n" + +// "- name: foo-group\n" + +// " properties:\n" + +// " <*>" ); } @@ -517,4 +518,82 @@ public class BoshEditorTest { editor.assertHoverContains("type", "Type of a variable"); editor.assertHoverContains("options", "Specifies generation options"); } -} + + @Test public void tolerateV1Manifests() throws Exception { + Editor editor = harness.newEditor( + "---\n" + + "name: my-redis-deployment\n" + + "director_uuid: 1234abcd-5678-efab-9012-3456cdef7890\n" + + "\n" + + "releases:\n" + + "- {name: redis, version: 12}\n" + + "\n" + + "resource_pools:\n" + + "- name: redis-servers\n" + + " network: default\n" + + " stemcell:\n" + + " name: bosh-aws-xen-ubuntu-trusty-go_agent\n" + + " version: 2708\n" + + " cloud_properties:\n" + + " instance_type: m1.small\n" + + " availability_zone: us-east-1c\n" + + "\n" + + "disk_pools: []\n" + + "\n" + + "networks:\n" + + "- name: default\n" + + " type: manual\n" + + " subnets:\n" + + " - range: 10.10.0.0/24\n" + + " gateway: 10.10.0.1\n" + + " static:\n" + + " - 10.10.0.16 - 10.10.0.18\n" + + " reserved:\n" + + " - 10.10.0.2 - 10.10.0.15\n" + + " dns: [10.10.0.6]\n" + + " cloud_properties:\n" + + " subnet: subnet-d597b993\n" + + "\n" + + "compilation:\n" + + " workers: 2\n" + + " network: default\n" + + " reuse_compilation_vms: true\n" + + " cloud_properties:\n" + + " instance_type: c1.medium\n" + + " availability_zone: us-east-1c\n" + + "\n" + + "update:\n" + + " canaries: 1\n" + + " max_in_flight: 3\n" + + " canary_watch_time: 15000-30000\n" + + " update_watch_time: 15000-300000\n" + + "\n" + + "jobs:\n" + + "- name: redis-master\n" + + " instances: 1\n" + + " templates:\n" + + " - {name: redis-server, release: redis}\n" + + " persistent_disk: 10_240\n" + + " resource_pool: redis-servers\n" + + " networks:\n" + + " - name: default\n" + + "\n" + + "- name: redis-slave\n" + + " instances: 2\n" + + " templates:\n" + + " - {name: redis-server, release: redis}\n" + + " persistent_disk: 10_240\n" + + " resource_pool: redis-servers\n" + + " networks:\n" + + " - name: default\n" + + "\n" + + "properties:\n" + + " redis:\n" + + " max_connections: 10\n" + + "\n" + + "cloud_provider: {}" + ); + editor.ignoreProblem(YamlSchemaProblems.DEPRECATED_PROPERTY); + editor.assertProblems(/*NONE*/); + } +} \ No newline at end of file diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java index 136e6df03..1145a5b53 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java @@ -70,18 +70,18 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { this.type = relaxedType; } - public YTypeAssistContext(YTypeAssistContext parent, YamlPath contextPath, YType YType, YTypeUtil typeUtil) { + public YTypeAssistContext(YTypeAssistContext parent, YamlPath contextPath, YType type, YTypeUtil typeUtil) { super(parent.getDocument(), parent.documentSelector, contextPath); this.parent = parent; this.typeUtil = typeUtil; - this.type = typeUtil.inferMoreSpecificType(YType, getSchemaContext()); + this.type = typeUtil.inferMoreSpecificType(type, getSchemaContext()); } public YTypeAssistContext(TopLevelAssistContext parent, int documentSelector, YType type, YTypeUtil typeUtil) { super(parent.getDocument(), documentSelector, YamlPath.EMPTY); this.parent = parent; this.typeUtil = typeUtil; - this.type = type; + this.type = typeUtil.inferMoreSpecificType(type, getSchemaContext()); } @Override @@ -114,9 +114,10 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { List> tieredProperties = sortIntoTiers(allProperties); Set definedProps = dynamicCtxt.getDefinedProperties(); List proposals = new ArrayList<>(); + boolean suggestDeprecated = typeUtil.suggestDeprecatedProperties(); for (List thisTier : tieredProperties) { List undefinedProps = thisTier.stream() - .filter(p -> !definedProps.contains(p.getName())) + .filter(p -> !definedProps.contains(p.getName()) && (suggestDeprecated || !p.isDeprecated())) .collect(Collectors.toList()); if (!undefinedProps.isEmpty()) { for (YTypedProperty p : undefinedProps) { diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java index 232f27439..0f00e3086 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java @@ -52,10 +52,8 @@ import reactor.core.publisher.Flux; */ public class YTypeFactory { - /** - * Configuration option for the type-based completion engine. - */ private boolean enableTieredOptionalPropertyProposals = true; + private boolean suggestDeprecatedProperties = true; private static class Deprecation { final String errorMsg; @@ -239,6 +237,11 @@ public class YTypeFactory { public boolean tieredOptionalPropertyProposals() { return enableTieredOptionalPropertyProposals; } + + @Override + public boolean suggestDeprecatedProperties() { + return suggestDeprecatedProperties; + } }; ///////////////////////////////////////////////////////////////////////////////////// @@ -916,4 +919,9 @@ public class YTypeFactory { return this; } + public YTypeFactory suggestDeprecatedProperties(boolean enable) { + this.suggestDeprecatedProperties = enable; + return this; + } + } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java index 02e884345..ca7d2fb7d 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java @@ -56,4 +56,10 @@ public interface YTypeUtil { * suggested until required ones are all defined) */ boolean tieredOptionalPropertyProposals(); + /** + * Config option for type-based completion engine. This enables/disables + * whether engine should generate proposals for deprecated properties (true), + * or suppress them (false). + */ + boolean suggestDeprecatedProperties(); }