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 ddf74bae2..69354bd12 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 @@ -14,6 +14,7 @@ import java.util.UUID; 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; @@ -55,8 +56,35 @@ public class BoshDeploymentManifestSchema implements YamlSchema { TOPLEVEL_TYPE = f.ybean("BoshDeploymentManifest"); addProp(TOPLEVEL_TYPE, "name", t_ne_string).isPrimary(true); - addProp(TOPLEVEL_TYPE, "director_uuid", t_uuid) - .isRequired(true); + addProp(TOPLEVEL_TYPE, "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." + ); + + YAtomicType t_network_name = f.yatomic("NetworkName"); //TODO: resolve from 'cloud config' https://www.pivotaltracker.com/story/show/148712155 + t_network_name.parseWith(ValueParsers.NE_STRING); + + YAtomicType t_disk_type = f.yatomic("DiskType"); //TODO: resolve from 'cloud config' https://www.pivotaltracker.com/story/show/148704001 + t_disk_type.parseWith(ValueParsers.NE_STRING); + + YAtomicType t_stemcell_alias = f.yatomic("StemcellAlias"); //TODO: resolve from 'stemcells block' https://www.pivotaltracker.com/story/show/148706041 + t_stemcell_alias.parseWith(ValueParsers.NE_STRING); + + YAtomicType t_vm_extension = f.yatomic("VMExtension"); //TODO: resolve dynamically from 'cloud config' ? https://www.pivotaltracker.com/story/show/148703877 + t_vm_extension.parseWith(ValueParsers.NE_STRING); + + YAtomicType t_vm_type = f.yatomic("VMType"); //TODO: resolve dynamically from 'cloud config' ? https://www.pivotaltracker.com/story/show/148686169 + t_vm_type.parseWith(ValueParsers.NE_STRING); + + YAtomicType t_az = f.yatomic("AvailabilityZone"); //TODO: resolve dynamically from 'cloud config': https://www.pivotaltracker.com/story/show/148704481 + t_az.parseWith(ValueParsers.NE_STRING); + + YBeanType t_network = f.ybean("Network"); + addProp(t_network, "name", t_network_name).isRequired(true); + + YBeanType t_instance_group_env = f.ybean("InstanceGroupEnv"); + addProp(t_instance_group_env, "npsh", t_params); + addProp(t_instance_group_env, "password", t_ne_string); YAtomicType t_version = f.yatomic("Version"); t_version.addHints("latest"); @@ -77,8 +105,31 @@ public class BoshDeploymentManifestSchema implements YamlSchema { YType t_update = t_params; //TODO: https://www.pivotaltracker.com/story/show/148627121 addProp(TOPLEVEL_TYPE, "update", t_update).isRequired(true); - - YType t_instance_group = t_params; //TODO: https://www.pivotaltracker.com/story/show/148627211 + + YBeanType t_job = f.ybean("Job"); + addProp(t_job, "name", t_ne_string).isPrimary(true); + addProp(t_job, "release", t_ne_string).isRequired(true); + addProp(t_job, "consumes", t_params); + addProp(t_job, "provides", t_params); + addProp(t_job, "properties", t_params).isRequired(true); + + YBeanType t_instance_group = f.ybean("InstanceGroup"); + addProp(t_instance_group, "name", t_ne_string).isPrimary(true); + addProp(t_instance_group, "azs", f.yseq(t_az)).isRequired(true); + addProp(t_instance_group, "instances", t_pos_integer).isRequired(true); //Strictly positive? Or zero is okay? + addProp(t_instance_group, "jobs", f.yseq(t_job)).isRequired(true); + addProp(t_instance_group, "vm_type", t_vm_type).isRequired(true); + addProp(t_instance_group, "vm_extensions", f.yseq(t_vm_extension)); + addProp(t_instance_group, "stemcell", t_stemcell_alias).isRequired(true); + addProp(t_instance_group, "persistent_disk_type", t_disk_type); + addProp(t_instance_group, "networks", f.yseq(t_network)); + addProp(t_instance_group, "update", t_update); + YType t_migration = t_params; //TODO: https://www.pivotaltracker.com/story/show/148712595 + addProp(t_instance_group, "migrated_from", f.yseq(t_migration)); + addProp(t_instance_group, "lifecycle", f.yenum("WorkloadType", "service", "errand")); + 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"); diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/azs.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/azs.md new file mode 100644 index 000000000..24d96bc03 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/azs.md @@ -0,0 +1 @@ +*Required*. List of AZs associated with this instance group (should only be used when using [first class AZs](https://bosh.io/docs/azs.html) ). Example: `[z1, z2]`. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/env.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/env.md new file mode 100644 index 000000000..b103f645f --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/env.md @@ -0,0 +1 @@ +*Optional*. Specifies advanced BOSH Agent configuration for each instance in the group. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/instances.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/instances.md new file mode 100644 index 000000000..3abbbf21f --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/instances.md @@ -0,0 +1 @@ +*Required*. The number of instances in this group. Each instance is a VM. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/jobs.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/jobs.md new file mode 100644 index 000000000..976280b35 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/jobs.md @@ -0,0 +1 @@ +*Required*. Specifies the name and release of jobs that will be installed on each instance. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/lifecycle.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/lifecycle.md new file mode 100644 index 000000000..35147f908 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/lifecycle.md @@ -0,0 +1 @@ +Specifies the kind of workload the instance group represents. Valid values are `service` and `errand`; defaults to `service`. A `service` runs indefinitely and restarts if it fails. An `errand` starts with a manual trigger and does not restart if it fails. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/migrated_from.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/migrated_from.md new file mode 100644 index 000000000..56274d3d1 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/migrated_from.md @@ -0,0 +1 @@ +*Optional*. Specific migration settings for this instance group. Use this to [rename and/or migrate instance groups](https://bosh.io/docs/migrated-from.html). \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/name.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/name.md new file mode 100644 index 000000000..42ee7729f --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/name.md @@ -0,0 +1 @@ +*Required*. A unique name used to identify and reference the instance group. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/networks.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/networks.md new file mode 100644 index 000000000..4312b0807 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/networks.md @@ -0,0 +1 @@ +*Required*. Specifies the networks this instance requires. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/persistent_disk_type.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/persistent_disk_type.md new file mode 100644 index 000000000..9f3b4b1a8 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/persistent_disk_type.md @@ -0,0 +1 @@ +*Optional*. A valid disk type name from the cloud config. [Read more about persistent disks](https://bosh.io/docs/persistent-disks.html) \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/properties.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/properties.md new file mode 100644 index 000000000..ea808a217 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/properties.md @@ -0,0 +1 @@ +*Optional*. Specifies instance group properties. Deprecated in favor of job level properties and links. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/stemcell.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/stemcell.md new file mode 100644 index 000000000..611ef5942 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/stemcell.md @@ -0,0 +1 @@ +*Required*. A valid stemcell alias from the Stemcells Block. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/update.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/update.md new file mode 100644 index 000000000..74f08e2f7 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/update.md @@ -0,0 +1 @@ +*Optional*. Specific update settings for this instance group. Use this to override [global job update settings](https://bosh.io/docs/manifest-v2.html#update) on a per-instance-group basis. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/vm_extensions.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/vm_extensions.md new file mode 100644 index 000000000..65528a7b2 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/vm_extensions.md @@ -0,0 +1 @@ +*Optional*. A valid list of VM extension names from the cloud config. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/vm_type.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/vm_type.md new file mode 100644 index 000000000..fd4d7c87a --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroup/vm_type.md @@ -0,0 +1 @@ +*Required*. A valid VM type name from the cloud config. \ No newline at end of file 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 713bae766..0a4d1830f 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 @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.concourse; 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; @@ -98,6 +99,7 @@ public class BoshEditorTest { "blah: hoooo\n" ); editor.assertProblems( + "director_uuid|bosh v2 CLI no longer checks or requires", "properties|Deprecated in favor of job level properties and links", "blah|Unknown property" ); @@ -128,13 +130,12 @@ public class BoshEditorTest { editor.assertCompletions( "name: blah\n" + - "director_uuid: <*>" + "instance_groups:\n" + + "- name: <*>" , // ============ "name: blah\n" + - "instance_groups:\n- <*>" - , // ============ - "name: blah\n" + - "releases:\n- <*>" + "releases:\n" + + "- name: <*>" , // ============ "name: blah\n" + "stemcells:\n- <*>" @@ -149,6 +150,9 @@ public class BoshEditorTest { "variables:\n- <*>" , // ============ "name: blah\n" + + "director_uuid: <*>" + , // ============ + "name: blah\n" + "properties:\n <*>" ); } @@ -186,7 +190,7 @@ public class BoshEditorTest { editor.assertProblems( "-|One of [name, os] is required", "-|[alias, version] are required", - "}|[director_uuid, instance_groups, name, releases, update] are required" + "}|[instance_groups, name, releases, update] are required" ); } @@ -236,12 +240,164 @@ public class BoshEditorTest { editor.assertCompletionLabels("latest"); } + @Test public void temp_instanceGroupsCompletions() throws Exception { + Editor editor = harness.newEditor( + "instance_groups:\n" + + "- name: foo-group\n" + + " job<*>" + ); + editor.assertCompletions( + "instance_groups:\n" + + "- name: foo-group\n" + + " jobs:\n" + + " - name: <*>" + ); + } + @Test public void instanceGroupsCompletions() throws Exception { Editor editor = harness.newEditor( "instance_groups:\n" + "- <*>" ); - + editor.assertCompletions( + "instance_groups:\n" + + "- name: <*>" + ); + + editor = harness.newEditor( + "instance_groups:\n" + + "- name: foo-group\n" + + " <*>" + ); + editor.assertCompletions(PLAIN_COMPLETION, + "instance_groups:\n" + + "- name: foo-group\n" + + " azs:\n" + + " - <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " env:\n" + + " <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " instances: <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " jobs:\n" + + " - name: <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " lifecycle: <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " migrated_from:\n" + + " - <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " networks:\n" + + " - name: <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " persistent_disk_type: <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " stemcell: <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " update:\n" + + " <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " vm_extensions:\n" + + " - <*>" + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " vm_type: <*>" + + , // ============= + "instance_groups:\n" + + "- name: foo-group\n" + + " properties:\n" + + " <*>" + ); } - + + @Test public void instanceGroupsHovers() throws Exception { + Editor editor = harness.newEditor( + "instance_groups:\n" + + "- name: redis-master\n" + + " properties: {}\n" + + " instances: 1\n" + + " azs: [z1, z2]\n" + + " jobs:\n" + + " - name: redis-server\n" + + " release: redis\n" + + " properties:\n" + + " port: 3606\n" + + " vm_type: medium\n" + + " vm_extensions: [public-lbs]\n" + + " stemcell: default\n" + + " persistent_disk_type: medium\n" + + " networks:\n" + + " - name: default\n" + + "\n" + + "- name: redis-slave\n" + + " instances: 2\n" + + " azs: [z1, z2]\n" + + " jobs:\n" + + " - name: redis-server\n" + + " release: redis\n" + + " properties: {}\n" + + " update:\n" + + " canaries: 2\n" + + " lifecycle: errand\n" + + " migrated_from: []\n" + + " env: {}\n" + + " vm_type: medium\n" + + " stemcell: default\n" + + " persistent_disk_type: medium\n" + + " networks:\n" + + " - name: default\n" + ); + editor.assertHoverContains("name", "A unique name used to identify and reference the instance group."); + editor.assertHoverContains("azs", "List of AZs associated with this instance group"); + editor.assertHoverContains("instances", "The number of instances in this group"); + editor.assertHoverContains("jobs", "Specifies the name and release of jobs that will be installed on each instance."); + editor.assertHoverContains("vm_type", "A valid VM type name from the cloud config"); + editor.assertHoverContains("vm_extensions", "A valid list of VM extension names from the cloud config"); + editor.assertHoverContains("stemcell", "A valid stemcell alias from the Stemcells Block"); + editor.assertHoverContains("persistent_disk_type", "A valid disk type name from the cloud config."); + editor.assertHoverContains("networks", "Specifies the networks this instance requires"); + editor.assertHoverContains("update", "Specific update settings for this instance group"); + editor.assertHoverContains("migrated_from", "Specific migration settings for this instance group."); + editor.assertHoverContains("lifecycle", "Specifies the kind of workload"); + editor.assertHoverContains("properties", "Specifies instance group properties"); + editor.assertHoverContains("env", "Specifies advanced BOSH Agent configuration"); + } + + @Ignore @Test public void instanceGroups_job_hovers() throws Exception { + //For the nested properties of instance_groups.jobs + throw new IllegalStateException("Implement a test please!"); + } + + @Ignore @Test public void instanceGroups_network_hovers() throws Exception { + //For the nested properties of instance_groups.networks + throw new IllegalStateException("Implement a test please!"); + } + + @Ignore @Test public void instanceGroups_env_hovers() throws Exception { + throw new IllegalStateException("Implement a test please!"); + } + } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AppendTextBuilder.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AppendTextBuilder.java index 59bd5a95a..80cbca01b 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AppendTextBuilder.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/AppendTextBuilder.java @@ -54,7 +54,7 @@ public class AppendTextBuilder { //ready to enter sequence element on next line newline(text, indent); text.append("- "); - singleRequiredProperty(typeUtil.getDomainType(type), indent+2, text); + singleMostImportantProperty(typeUtil.getDomainType(type), indent+2, text); //Yes using 2 here instead of YamlIndentUtil.INDENT_BY is deliberate. It's the same value (now), // but the 2 used here is the width of the "- " which should determine nested indent level for things to // line up properly. @@ -66,14 +66,18 @@ public class AppendTextBuilder { } } - private void singleRequiredProperty(YType type, int indent, StringBuilder text) { + private void singleMostImportantProperty(YType type, int indent, StringBuilder text) { if (type!=null) { - YTypedProperty requireProp = Streams.getSingle(typeUtil.getProperties(type).stream() - .filter(p -> p.isRequired())); - if (requireProp!=null) { - text.append(requireProp.getName()); + YTypedProperty singleProp = Streams.getSingle(typeUtil.getProperties(type).stream() + .filter(p -> p.isPrimary())); + if (singleProp==null) { + singleProp = Streams.getSingle(typeUtil.getProperties(type).stream() + .filter(p -> p.isRequired())); + } + if (singleProp!=null) { + text.append(singleProp.getName()); text.append(':'); - build(requireProp.getType(), indent+YamlIndentUtil.INDENT_BY, text); + build(singleProp.getType(), indent+YamlIndentUtil.INDENT_BY, text); } } } diff --git a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java index fdece2a34..4c1221d9c 100644 --- a/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java +++ b/headless-services/concourse-language-server/src/test/java/org/springframework/ide/vscode/concourse/ConcourseEditorTest.java @@ -473,7 +473,7 @@ public class ConcourseEditorTest { editor = harness.newEditor("jo<*>"); editor.assertCompletions( "jobs:\n"+ - "- <*>" + "- name: <*>" ); } @@ -486,19 +486,19 @@ public class ConcourseEditorTest { "- name: <*>" , // -------------- "jobs:\n" + - "- <*>" + "- name: <*>" , // --------------- "resource_types:\n" + - "- <*>" + "- name: <*>" , // --------------- "resources:\n"+ - "- <*>" + "- name: <*>" ); editor = harness.newEditor("rety<*>"); editor.assertCompletions( "resource_types:\n" + - "- <*>" + "- name: <*>" ); }