diff --git a/concourse/pipeline.yml b/concourse/pipeline.yml index 4f0742966..5b6347c8d 100644 --- a/concourse/pipeline.yml +++ b/concourse/pipeline.yml @@ -79,6 +79,14 @@ resources: secret_access_key: {{s3_secretkey}} region_name: {{s3_region}} regexp: sts4/vscode-extensions/snapshots/vscode-manifest-yaml-(.*).vsix +- name: s3-manifest-yaml-fatjar-snapshot + type: s3 + source: + bucket: {{s3_bucket}} + access_key_id: {{s3_accesskey}} + secret_access_key: {{s3_secretkey}} + region_name: {{s3_region}} + regexp: sts4/fatjars/snapshots/manifest-yaml-language-server-(.*).jar - name: snapshot-website type: s3-multi source: @@ -232,10 +240,15 @@ jobs: params: text: | Concourse ${BUILD_PIPELINE_NAME}/${BUILD_JOB_NAME}/${BUILD_NAME} has failed! - - put: s3-manifest-yaml-vsix-snapshot - params: - file: out/vscode-manifest-yaml-*.vsix - acl: public-read + - aggregate: + - put: s3-manifest-yaml-vsix-snapshot + params: + file: out/vscode-manifest-yaml-*.vsix + acl: public-read + - put: s3-manifest-yaml-fatjar-snapshot + params: + file: out/*.jar + acl: public-read - name: trigger-rc-build serial: true plan: diff --git a/concourse/tasks/build-vsix.sh b/concourse/tasks/build-vsix.sh index d799492f1..d120033aa 100755 --- a/concourse/tasks/build-vsix.sh +++ b/concourse/tasks/build-vsix.sh @@ -2,6 +2,7 @@ set -e workdir=`pwd` sources=$workdir/sts4/vscode-extensions/$extension_id +server_id=${extension_id#vscode-} if [ -d "maven-cache" ]; then echo "Prepopulating maven cache" @@ -48,3 +49,4 @@ if [ "$dist_type" == release ]; then fi cp *.vsix $workdir/out +cp $workdir/sts4/headless-services/${server_id}-language-server/target/*.jar $workdir/out/${server_id}-language-server-${base_version}-${timestamp}.jar diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypedProperty.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypedProperty.java index ff600bb02..ae834f5e9 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypedProperty.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypedProperty.java @@ -103,4 +103,5 @@ public class TypedProperty implements YTypedProperty { public Deprecation getDeprecation() { return deprecation; } + } diff --git a/headless-services/bosh-language-server/.settings/org.eclipse.jdt.ui.prefs b/headless-services/bosh-language-server/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..75f829439 --- /dev/null +++ b/headless-services/bosh-language-server/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,59 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/headless-services/bosh-language-server/pom.xml b/headless-services/bosh-language-server/pom.xml index 2f00f5c1f..cbd6d6d60 100644 --- a/headless-services/bosh-language-server/pom.xml +++ b/headless-services/bosh-language-server/pom.xml @@ -45,6 +45,18 @@ ${dependencies.version} test + + org.mockito + mockito-all + ${mockito-version} + test + + + org.springframework.ide.vscode + language-server-test-harness + ${dependencies.version} + test + diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshConstraints.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshConstraints.java new file mode 100644 index 000000000..5c80ef16d --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshConstraints.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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.bosh; + +import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems.MISSING_PROPERTY; +import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems.problem; + +import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; +import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; +import org.springframework.ide.vscode.commons.yaml.schema.YType; +import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.NodeTuple; + +public class BoshConstraints { + + public static final Constraint SHA1_REQUIRED_FOR_HTTP_URL = new Constraint() { + + @Override + public void verify(DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) { + NodeTuple urlProp = NodeUtil.getPropertyTuple(node, "url"); + if (urlProp!=null) { + String url = NodeUtil.asScalar(urlProp.getValueNode()); + if (url!=null && url.startsWith("http")) { + Node sha1 = NodeUtil.getProperty(node, "sha1"); + if (sha1==null) { + problems.accept(problem(MISSING_PROPERTY, "'sha1' is required when the 'url' is http(s)", urlProp.getKeyNode())); + } + } + } + } + }; + +} 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..af7150e9d 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,30 +10,52 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh; +import java.util.Collection; +import java.util.Map; import java.util.UUID; +import org.springframework.ide.vscode.bosh.models.CachingModelProvider; +import org.springframework.ide.vscode.bosh.models.CloudConfigModel; +import org.springframework.ide.vscode.bosh.models.DynamicModelProvider; +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.ValueParsers; +import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache; +import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; 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 org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints; + +import com.google.common.collect.ImmutableList; +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"); + private ImmutableList definitionTypes = null; + //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); @@ -49,45 +71,180 @@ public class BoshDeploymentManifestSchema implements YamlSchema { public final YType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer") .parseWith(ValueParsers.integerAtLeast(1)); public final YType t_uuid = f.yatomic("UUID").parseWith(UUID::fromString); + public final YType t_integer_or_range = f.yatomic("Integer or Range") + .parseWith(BoshValueParsers.INTEGER_OR_RANGE); + private YType t_instance_group_name_def; + private YType t_stemcell_alias_def; + private YType t_stemcell_alias_ref; + private YType t_release_name_def; + private YType t_release_name_ref; + private YType t_var_name_def; + private final ASTTypeCache astTypes; + private DynamicModelProvider cloudConfigProvider; - public BoshDeploymentManifestSchema() { + public BoshDeploymentManifestSchema(ASTTypeCache astTypes, DynamicModelProvider cloudConfigProvider) { + this.astTypes = astTypes; + this.cloudConfigProvider = new CachingModelProvider<>(cloudConfigProvider); 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) - .isRequired(true); + + 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." + ); + + t_instance_group_name_def = f.yatomic("InstanceGroupName") + .parseWith(ValueParsers.NE_STRING); + + t_stemcell_alias_def = f.yatomic("StemcellAlias") + .parseWith(ValueParsers.NE_STRING); + t_stemcell_alias_ref = f.yenumFromDynamicValues("StemcellAlias", (dc) -> astTypes.getDefinedNames(dc, t_stemcell_alias_def)); + + t_release_name_def = f.yatomic("ReleaseName") + .parseWith(ValueParsers.NE_STRING); + t_release_name_ref = f.yenumFromDynamicValues("ReleaseName", (dc) -> astTypes.getDefinedNames(dc, t_release_name_def)); + + t_var_name_def = f.yatomic("VariableName") + .parseWith(ValueParsers.NE_STRING); + + YAtomicType t_ip_address = f.yatomic("IPAddress"); //TODO: some kind of checking? + t_ip_address.parseWith(ValueParsers.NE_STRING); + + YAtomicType t_url = f.yatomic("URL"); + t_url.parseWith(BoshValueParsers.url("http", "https", "file")); + + 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_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.yenumFromDynamicValues("VMType", (dc) -> cloudConfigProvider.getModel(dc).getVMTypes()); + + 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); + addProp(t_network, "static_ips", f.yseq(t_ip_address)); + addProp(t_network, "default", f.yseq(t_ne_string)); //TODO: Can we determine the set of valid values? How? + + YBeanType t_instance_group_env = f.ybean("InstanceGroupEnv"); + addProp(t_instance_group_env, "bosh", t_params); + addProp(t_instance_group_env, "password", t_ne_string); YAtomicType t_version = f.yatomic("Version"); t_version.addHints("latest"); t_version.parseWith(ValueParsers.NE_STRING); 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(t_release, "name", t_release_name_def).isPrimary(true); + addProp(t_release, "version", t_version); + //TODO: the checking here is just 'my best guess'. Unclarity remains: + // See: https://github.com/cloudfoundry/docs-bosh/issues/330 + addProp(t_release, "url", t_url); + addProp(t_release, "sha1", t_ne_string); + addProp(v2Schema, "releases", f.yseq(t_release)).isRequired(true); + t_release.require(Constraints.requireAtLeastOneOf("url", "version")); + // ^^^^^^^ allthough docs seem to imply you shouldn't + // define both url and version it seems that bosh tolerates it. + t_release.require(BoshConstraints.SHA1_REQUIRED_FOR_HTTP_URL); + YBeanType t_stemcell = f.ybean("Stemcell"); - addProp(t_stemcell, "alias", t_ne_string).isRequired(true); + addProp(t_stemcell, "alias", t_stemcell_alias_def).isRequired(true); addProp(t_stemcell, "version", t_ne_string).isRequired(true); 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); - 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 - 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"); - - YType t_variable = t_params; //TODO: https://www.pivotaltracker.com/story/show/148627441 - addProp(TOPLEVEL_TYPE, "variables", f.yseq(t_variable)); + YBeanType t_update = f.ybean("Update"); + addProp(t_update, "canaries", t_strictly_pos_integer).isRequired(true); + addProp(t_update, "max_in_flight", t_pos_integer).isRequired(true); + 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(v2Schema, "update", t_update).isRequired(true); - addProp(TOPLEVEL_TYPE, "tags", t_params); - + YBeanType t_job = f.ybean("Job"); + addProp(t_job, "name", t_ne_string).isRequired(true); + addProp(t_job, "release", t_release_name_ref).isRequired(true); + addProp(t_job, "consumes", t_params); + addProp(t_job, "provides", t_params); + addProp(t_job, "properties", t_params); + + YBeanType t_instance_group = f.ybean("InstanceGroup"); + addProp(t_instance_group, "name", t_instance_group_name_def).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_ref).isRequired(true); + addProp(t_instance_group, "persistent_disk_type", t_disk_type); + addProp(t_instance_group, "networks", f.yseq(t_network)).isRequired(true); + YType t_update_override = f.ybean("UpdateOverrides", t_update.getProperties() + .stream() + .map((YTypedProperty prop) -> + f.yprop(prop).isRequired(false) + ) + .toArray(sz -> new YTypedProperty[sz]) + ); + addProp(t_instance_group, "update", t_update_override); + 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(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_var_name_def).isPrimary(true); + addProp(t_variable, "type", f.yenum("VariableType", "certificate", "password", "rsa", "ssh")).isRequired(true); + addProp(t_variable, "options", t_params); + addProp(v2Schema, "variables", f.yseq(t_variable)); + + 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"); + } + + for (YType defType : getDefinitionTypes()) { + v2Schema.require(Constraints.uniqueDefinition(this.astTypes, defType, YamlSchemaProblems.problemType("BOSH_DUPLICATE_"+defType))); + } + return v2Schema; } @Override @@ -99,13 +256,13 @@ public class BoshDeploymentManifestSchema implements YamlSchema { public YTypeUtil getTypeUtil() { return TYPE_UTIL; } - + private YTypedPropertyImpl prop(AbstractType beanType, String name, YType type) { YTypedPropertyImpl prop = f.yprop(name, type); prop.setDescriptionProvider(descriptionFor(beanType, name)); return prop; } - + public static Renderable descriptionFor(YType owner, String propName) { String typeName = owner.toString(); return Renderables.fromClasspath(BoshDeploymentManifestSchema.class, "/desc/"+typeName+"/"+propName); @@ -121,4 +278,16 @@ public class BoshDeploymentManifestSchema implements YamlSchema { return p; } + public Collection getDefinitionTypes() { + if (definitionTypes==null) { + definitionTypes = ImmutableList.of( + t_instance_group_name_def, + t_stemcell_alias_def, + t_release_name_def, + t_var_name_def + ); + } + return definitionTypes; + } + } diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java index 8f92aeed4..2ae708e9f 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshLanguageServer.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.bosh; +import org.springframework.ide.vscode.bosh.models.CloudConfigModel; +import org.springframework.ide.vscode.bosh.models.DynamicModelProvider; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter.LazyCompletionResolver; import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; @@ -28,8 +30,9 @@ import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngi import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngineOptions; import org.springframework.ide.vscode.commons.yaml.hover.YamlHoverInfoProvider; import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes; +import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache; +import org.springframework.ide.vscode.commons.yaml.reconcile.TypeBasedYamlSymbolHandler; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine; -import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema; import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureProvider; import org.yaml.snakeyaml.Yaml; @@ -39,11 +42,12 @@ public class BoshLanguageServer extends SimpleLanguageServer { private final LazyCompletionResolver completionResolver = new LazyCompletionResolver(); //Set to null to disable lazy resolving private final VscodeCompletionEngineAdapter completionEngine; - public BoshLanguageServer() { + public BoshLanguageServer(DynamicModelProvider cloudConfigProvider) { super("vscode-bosh"); YamlASTProvider parser = new YamlParser(yaml); SimpleTextDocumentService documents = getTextDocumentService(); - YamlSchema schema = new BoshDeploymentManifestSchema(); + ASTTypeCache astTypeCache = new ASTTypeCache(); + BoshDeploymentManifestSchema schema = new BoshDeploymentManifestSchema(astTypeCache, cloudConfigProvider); YamlStructureProvider structureProvider = YamlStructureProvider.DEFAULT; YamlAssistContextProvider contextProvider = new SchemaBasedYamlAssistContextProvider(schema); @@ -53,7 +57,9 @@ public class BoshLanguageServer extends SimpleLanguageServer { HoverInfoProvider infoProvider = new YamlHoverInfoProvider(parser, structureProvider, contextProvider); VscodeHoverEngine hoverEngine = new VscodeHoverEngineAdapter(this, infoProvider); YamlQuickfixes quickfixes = new YamlQuickfixes(getQuickfixRegistry(), getTextDocumentService(), structureProvider); - IReconcileEngine engine = new YamlSchemaBasedReconcileEngine(parser, schema, quickfixes); + YamlSchemaBasedReconcileEngine engine = new YamlSchemaBasedReconcileEngine(parser, schema, quickfixes); + engine.setTypeCollector(astTypeCache); + documents.onDocumentSymbol(new TypeBasedYamlSymbolHandler(documents, astTypeCache, schema.getDefinitionTypes())); documents.onDidChangeContent(params -> { validateOnDocumentChange(engine, params.getDocument()); diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshValueParsers.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshValueParsers.java new file mode 100644 index 000000000..58eda3b46 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshValueParsers.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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.bosh; + +import java.net.URI; + +import org.springframework.ide.vscode.commons.util.ValueParseException; +import org.springframework.ide.vscode.commons.util.ValueParser; + +import com.google.common.collect.ImmutableList; + +public class BoshValueParsers { + + public static ValueParser url(String... _schemes) { + return new ValueParser() { + + private final ImmutableList validSchemes = ImmutableList.copyOf(_schemes); + + @Override + public Object parse(String s) throws Exception { + URI uri = new URI(s); + String scheme = uri.getScheme(); + if (scheme==null) { + throw new ValueParseException(message()); + } else if (!validSchemes.contains(scheme.toLowerCase())) { + int start = s.indexOf(scheme); + if (start>=0) { + int end = start + scheme.length(); + throw new ValueParseException(message(), start, end); + } else { + // Trouble finding exact location of underlined region so underline whole url + throw new ValueParseException(message()); + } + } + return uri; + } + + private String message() { + return "Url scheme must be one of "+validSchemes; + } + }; + } + + public static final ValueParser INTEGER_OR_RANGE = new ValueParser() { + + private int findDash(String s) throws ValueParseException { + int firstDash = s.indexOf('-'); + if (firstDash<0) { + return firstDash; //no dash... and that's okay! + } + int secondDash = s.indexOf('-', firstDash+1); + if (secondDash>=0) { + //Only one dash is expected! + throw new ValueParseException("Should be either a Integer, or a range (of the form '-')"); + } + return firstDash; + } + + @Override + public Object parse(String s) throws Exception { + int dash = findDash(s); + if (dash>=0) { + int low, high; + //range + try { + low = Integer.parseInt(s.substring(0, dash)); + } catch (Exception e) { + throw new ValueParseException("Should be a Integer", 0, dash); + } + try { + high = Integer.parseInt(s.substring(dash+1)); + } catch (Exception e) { + throw new ValueParseException("Should be a Integer", dash+1, s.length()); + } + if (low>high) { + throw new ValueParseException(low + " should be smaller than "+high); + } + } else { + //integer + try { + return Integer.parseInt(s); + } catch (Exception e) { + throw new ValueParseException("Should be either a Integer, or a range (of the form '-')"); + } + } + return s; + } + }; + + +} diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java index ecdbf2c3c..7e1a1403d 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/Main.java @@ -8,16 +8,15 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ - package org.springframework.ide.vscode.bosh; import java.io.IOException; +import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider; import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; -import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngineOptions; public class Main { public static void main(String[] args) throws IOException, InterruptedException { - LaunguageServerApp.start(() -> new BoshLanguageServer()); + LaunguageServerApp.start(() -> new BoshLanguageServer(new BoshCommandCloudConfigProvider())); } } diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProvider.java new file mode 100644 index 000000000..cf75725d6 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/BoshCommandCloudConfigProvider.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * 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.bosh.models; + +import java.io.File; +import java.time.Duration; +import java.util.Collection; +import java.util.stream.Stream; + +import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.CollectorUtil; +import org.springframework.ide.vscode.commons.util.ExternalCommand; +import org.springframework.ide.vscode.commons.util.ExternalProcess; +import org.springframework.ide.vscode.commons.util.StringUtil; +import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.commons.util.text.TextDocument; +import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; +import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; +import org.springframework.ide.vscode.commons.yaml.ast.YamlParser; +import org.springframework.ide.vscode.commons.yaml.path.YamlPath; +import org.springframework.ide.vscode.commons.yaml.path.YamlTraversal; +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.representer.Representer; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Concrete implementation of {@link CloudConfigProvider} that runs `bosh cloud-config` command + * with bosh cli and parses the output. + * + * @author Kris De Volder + */ +public class BoshCommandCloudConfigProvider implements DynamicModelProvider { + + ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + final YamlParser yamlParser; + + private Duration CMD_TIMEOUT = Duration.ofSeconds(10); + + public BoshCommandCloudConfigProvider() { + Representer representer = new Representer(); + representer.getPropertyUtils().setSkipMissingProperties(true); + yamlParser = new YamlParser(new Yaml()); + } + + /** + * For deserializing the output from bosh cloud-config command. + */ + public static class CloudConfigResponse { + private String[] blocks; + + @JsonProperty("Blocks") + public String[] getBlocks() { + return blocks; + } + + public void setBlocks(String[] blocks) { + this.blocks = blocks; + } + } + + YamlTraversal VM_TYPE_NAMES = YamlPath.EMPTY + .thenAnyChild() + .thenValAt("vm_types") + .thenAnyChild() + .thenValAt("name"); + + @Override + public CloudConfigModel getModel(DynamicSchemaContext dc) throws Exception { + String out = executeBoshCloudConfigCommand(); + CloudConfigResponse response = mapper.readValue(out, CloudConfigResponse.class); + String[] blocks = response.getBlocks(); + Assert.isLegal(blocks!=null); + Assert.isLegal(blocks.length==1); + TextDocument doc = new TextDocument(null, LanguageId.BOSH_CLOUD_CONFIG); + doc.setText(blocks[0]); + YamlFileAST ast = yamlParser.getAST(doc); + return new CloudConfigModel() { + @Override + public Collection getVMTypes() { + return VM_TYPE_NAMES.traverseAmbiguously(ast) + .flatMap(nameNode -> { + String name = NodeUtil.asScalar(nameNode); + return StringUtil.hasText(name) + ? Stream.of(name) + : Stream.empty(); + }) + .collect(CollectorUtil.toMultiset()); + } + }; + } + + /** + * Configure how long we wait for the command to fetch cloud config before + * raising timeout exception. (The command may block for long amounts of time + * of the director is unreachable on the network). + */ + public void setCommandTimeout(Duration duration) { + this.CMD_TIMEOUT = duration; + } + + protected String executeBoshCloudConfigCommand() throws Exception { + ExternalCommand command = new ExternalCommand("bosh", "cloud-config", "--json"); + ExternalProcess process = new ExternalProcess(new File(".").getAbsoluteFile(), command, true, CMD_TIMEOUT); + System.out.println("executeBoshCloudConfigCommand: "+process); + String out = process.getOut(); + return out; + } + +} diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java new file mode 100644 index 000000000..c843daed7 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * 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.bosh.models; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +/** + * Wraps around a {@link DynamicModelProvider} to add a cache. + */ +public class CachingModelProvider implements DynamicModelProvider { + + /** + * Special key to use when the actual key is null (because guava cache doesn't + * like null keys). + */ + private static final Object NULL_KEY = new Object(); + + private long timeout = 15; + private TimeUnit timeoutUnit = TimeUnit.SECONDS; + private Cache cache = createCache(); + + private final DynamicModelProvider delegate; + + public CachingModelProvider(DynamicModelProvider delegate) { + this.delegate = delegate; + } + + /** + * Function used to determine the caching key, given the current {@link DynamicSchemaContext}. + *

+ * The default keyGetter ignores the context and just returns the same object all the time. This + * results in a cache that only keeps a single value (since there's only a single key ever used + * to store / find cache entries. + */ + private Function keyGetter = (dc) -> "WHATEVER"; + + protected Cache createCache() { + return CacheBuilder.newBuilder() + .expireAfterWrite(timeout, timeoutUnit) + .build(); + } + + public CachingModelProvider setTimeout(long timeout, TimeUnit unit) { + this.timeout = timeout; + this.timeoutUnit = unit; + return this; + } + + @Override + public T getModel(DynamicSchemaContext dc) throws Exception { + Object key = keyGetter.apply(dc); + if (key==null) { + //guava cache doesn't like null key + key = NULL_KEY; + } + return cache.get(key, () -> delegate.getModel(dc)); + } + +} diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CloudConfigModel.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CloudConfigModel.java new file mode 100644 index 000000000..c90c2393e --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CloudConfigModel.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * 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.bosh.models; + +import java.util.Collection; + +/** + * Represents CloudConfig information as might be retrieved from bosh director. + */ +public interface CloudConfigModel { + Collection getVMTypes(); +} diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/DynamicModelProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/DynamicModelProvider.java new file mode 100644 index 000000000..27c817576 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/DynamicModelProvider.java @@ -0,0 +1,10 @@ +package org.springframework.ide.vscode.bosh.models; + +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; + +/** + * Responsible for somehow obtaining {@link CloudConfigModel} relative to a {@link DynamicSchemaContext} + */ +public interface DynamicModelProvider { + T getModel(DynamicSchemaContext dc) throws Exception; +} 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/main/resources/desc/InstanceGroupEnv/password.md b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroupEnv/password.md new file mode 100644 index 000000000..72d5ca760 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/InstanceGroupEnv/password.md @@ -0,0 +1 @@ +*Optional*. Crypted password for `vcap/root` user (will be placed into `/etc/shadow` on Linux). \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Job/consumes.md b/headless-services/bosh-language-server/src/main/resources/desc/Job/consumes.md new file mode 100644 index 000000000..b211822d3 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Job/consumes.md @@ -0,0 +1 @@ +*Optional*. Links consumed by the job. [Read more about link configuration](https://bosh.io/docs/links.html#deployment) \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Job/name.md b/headless-services/bosh-language-server/src/main/resources/desc/Job/name.md new file mode 100644 index 000000000..2848e8645 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Job/name.md @@ -0,0 +1 @@ +*Required*. The job name. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Job/properties.md b/headless-services/bosh-language-server/src/main/resources/desc/Job/properties.md new file mode 100644 index 000000000..475a4d082 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Job/properties.md @@ -0,0 +1 @@ +*Optional*. Specifies job properties. Properties allow BOSH to configure jobs to a specific environment. `properties` defined in a Job block are accessible only to that job. Only properties specified here will be provided to the job. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Job/provides.md b/headless-services/bosh-language-server/src/main/resources/desc/Job/provides.md new file mode 100644 index 000000000..170252147 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Job/provides.md @@ -0,0 +1 @@ +*Optional*. Links provided by the job. [Read more about link configuration](https://bosh.io/docs/links.html#deployment) \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Job/release.md b/headless-services/bosh-language-server/src/main/resources/desc/Job/release.md new file mode 100644 index 000000000..06e99e724 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Job/release.md @@ -0,0 +1 @@ +*Required*. The release where the job exists \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Network/default.md b/headless-services/bosh-language-server/src/main/resources/desc/Network/default.md new file mode 100644 index 000000000..03e0b92e9 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Network/default.md @@ -0,0 +1 @@ +*Optional*. Specifies which network components (DNS, Gateway) BOSH populates by default from this network. This property is required if more than one network is specified. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Network/name.md b/headless-services/bosh-language-server/src/main/resources/desc/Network/name.md new file mode 100644 index 000000000..d8b6e12b3 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Network/name.md @@ -0,0 +1 @@ +*Required*. A valid network name from the cloud config. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Network/static_ips.md b/headless-services/bosh-language-server/src/main/resources/desc/Network/static_ips.md new file mode 100644 index 000000000..eaced2217 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Network/static_ips.md @@ -0,0 +1 @@ +*Optional*. Array of IP addresses reserved for the instances on the network. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Release/sha1.md b/headless-services/bosh-language-server/src/main/resources/desc/Release/sha1.md new file mode 100644 index 000000000..fb04fe343 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Release/sha1.md @@ -0,0 +1 @@ +The SHA1 of the release tarball. SHA1 is only required when using HTTP(s) URLs. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Release/url.md b/headless-services/bosh-language-server/src/main/resources/desc/Release/url.md new file mode 100644 index 000000000..ee0df6ea5 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Release/url.md @@ -0,0 +1 @@ +URL of the release to use. URL may use the file protocol (`file://`) or HTTP(s) (`http(s)://`). File URLs can be absolute or relative to the current directory of `bosh-init` execution. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Update/canaries.md b/headless-services/bosh-language-server/src/main/resources/desc/Update/canaries.md new file mode 100644 index 000000000..ceb7725cf --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Update/canaries.md @@ -0,0 +1 @@ +*Required*. The number of [canary](https://bosh.io/docs/terminology.html#canary) instances. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Update/canary_watch_time.md b/headless-services/bosh-language-server/src/main/resources/desc/Update/canary_watch_time.md new file mode 100644 index 000000000..bba938372 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Update/canary_watch_time.md @@ -0,0 +1,6 @@ +*Required*. Only applies to monit start operation. + +- If the `canary_watch_time` is an integer, the Director sleeps for that many milliseconds, then checks whether the canary instances are healthy. +- If the `canary_watch_time` is a range (low-high), the Director: + - Waits for `low` milliseconds + - Waits until instances are healthy or `high` milliseconds have passed since instances started updating diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Update/max_in_flight.md b/headless-services/bosh-language-server/src/main/resources/desc/Update/max_in_flight.md new file mode 100644 index 000000000..dbf66283b --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Update/max_in_flight.md @@ -0,0 +1 @@ +*Required*. The maximum number of non-canary instances to update in parallel. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Update/serial.md b/headless-services/bosh-language-server/src/main/resources/desc/Update/serial.md new file mode 100644 index 000000000..60ad4d83b --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Update/serial.md @@ -0,0 +1 @@ +If disabled (set to `false`), instance groups will be deployed in parallel, otherwise - sequentially. Instances within a group will still follow `canary` and `max_in_flight` configuration. Defaults to `true`. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Update/update_watch_time.md b/headless-services/bosh-language-server/src/main/resources/desc/Update/update_watch_time.md new file mode 100644 index 000000000..fdd4c2898 --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Update/update_watch_time.md @@ -0,0 +1,6 @@ +*Required*. Only applies to monit start operation. + +- If the `update_watch_time` is an integer, the Director sleeps for that many milliseconds, then checks whether the instances are healthy. +- If the `update_watch_time` is a range (low-high), the Director: + - Waits for `low` milliseconds + - Waits until instances are healthy or `high` milliseconds have passed since instances started updating diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Variable/name.md b/headless-services/bosh-language-server/src/main/resources/desc/Variable/name.md new file mode 100644 index 000000000..7e246863d --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Variable/name.md @@ -0,0 +1 @@ +*Required*. Unique name used to identify a variable. Example: `admin_password` \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Variable/options.md b/headless-services/bosh-language-server/src/main/resources/desc/Variable/options.md new file mode 100644 index 000000000..a2c5da4ad --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Variable/options.md @@ -0,0 +1 @@ +*Optional*. Specifies generation options used for generating variable value if variable is not found. Example: `{is_ca: true, common_name: some-ca}` \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/main/resources/desc/Variable/type.md b/headless-services/bosh-language-server/src/main/resources/desc/Variable/type.md new file mode 100644 index 000000000..611892b5b --- /dev/null +++ b/headless-services/bosh-language-server/src/main/resources/desc/Variable/type.md @@ -0,0 +1 @@ +*Required*. Type of a variable. Currently supported variable types are `certificate`, `password`, `rsa`, and `ssh`. Example: `password`. \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java new file mode 100644 index 000000000..ecc3c5667 --- /dev/null +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshCommandCloudConfigProviderTest.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * 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.bosh; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider; +import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider; +import org.springframework.ide.vscode.bosh.models.CloudConfigModel; +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; + +import com.google.common.collect.ImmutableMultiset; + +public class BoshCommandCloudConfigProviderTest { + + public final MockCloudConfigProvider mockProvider = new MockCloudConfigProvider(); + +// For local testing only... in CI builds we don't have the means to use a real bosh director and cli. +// private BoshCommandCloudConfigProvider realProvider = new BoshCommandCloudConfigProvider(); + + @Test public void getVMTypes() throws Exception { + BoshCommandCloudConfigProvider provider = mockProvider; + DynamicSchemaContext dc = Mockito.mock(DynamicSchemaContext.class); + CloudConfigModel cloudConfig = provider.getModel(dc); + + assertEquals(ImmutableMultiset.of("default", "large"), cloudConfig.getVMTypes()); + } + +} diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java new file mode 100644 index 000000000..025d19d18 --- /dev/null +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java @@ -0,0 +1,923 @@ +/******************************************************************************* + * 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.bosh; + +import static org.junit.Assert.assertEquals; +import static org.springframework.ide.vscode.languageserver.testharness.Editor.PLAIN_COMPLETION; +import static org.springframework.ide.vscode.languageserver.testharness.TestAsserts.assertContains; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.lsp4j.CompletionItem; +import org.junit.Before; +import org.junit.Test; +import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider; +import org.springframework.ide.vscode.commons.util.text.LanguageId; +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; + + +public class BoshEditorTest { + + LanguageServerHarness harness; + + private MockCloudConfigProvider cloudConfigProvider = new MockCloudConfigProvider(); + + @Before public void setup() throws Exception { + harness = new LanguageServerHarness(() -> { + return new BoshLanguageServer(cloudConfigProvider) + .setMaxCompletions(100); + }, + LanguageId.BOSH_DEPLOYMENT + ); + harness.intialize(null); + } + + @Test public void toplevelV2PropertyNamesKnown() throws Exception { + Editor editor = harness.newEditor( + "name: some-name\n" + + "director_uuid: cf8dc1fc-9c42-4ffc-96f1-fbad983a6ce6\n" + + "releases:\n" + + "- name: redis\n" + + " version: 12\n" + + "stemcells:\n" + + "- alias: default\n" + + " os: ubuntu-trusty\n" + + " version: 3421.11\n" + + "update:\n" + + " canaries: 1\n" + + " max_in_flight: 10\n" + + " canary_watch_time: 1000-30000\n" + + " update_watch_time: 1000-30000\n" + + "instance_groups:\n" + + "- name: redis-master\n" + + " instances: 1\n" + + " azs: [z1, z2]\n" + + " jobs:\n" + + " - name: redis-server\n" + + " release: redis\n" + + " properties:\n" + + " port: 3606\n" + + " vm_type: large\n" + + " vm_extensions: [public-lbs]\n" + + " stemcell: default\n" + + " persistent_disk_type: medium\n" + + " networks:\n" + + " - name: default\n" + + "- name: redis-slave\n" + + " instances: 2\n" + + " azs: [z1, z2]\n" + + " jobs:\n" + + " - name: redis-server\n" + + " release: redis\n" + + " properties: {}\n" + + " vm_type: large\n" + + " stemcell: default\n" + + " persistent_disk_type: large\n" + + " networks:\n" + + " - name: default\n" + + "variables:\n" + + "- name: admin_password\n" + + " type: password\n" + + "- name: default_ca\n" + + " type: certificate\n" + + " options:\n" + + " is_ca: true\n" + + " common_name: some-ca\n" + + "- name: director_ssl\n" + + " type: certificate\n" + + " options:\n" + + " ca: default_ca\n" + + " common_name: cc.cf.internal\n" + + " alternative_names: [cc.cf.internal]\n" + + "properties:\n" + + " a-property: the-value\n" + + "tags:\n" + + " project: cf\n" + + "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" + ); + + editor.assertHoverContains("name", "The name of the deployment"); + editor.assertHoverContains("director_uuid", "This string must match the UUID of the currently targeted Director"); + editor.assertHoverContains("releases", "The name and version of each release in the deployment"); + editor.assertHoverContains("stemcells", "The name and version of each stemcell"); + editor.assertHoverContains("update", "This specifies instance update properties"); + editor.assertHoverContains("instance_groups", "Specifies the mapping between release [jobs](https://bosh.io/docs/terminology.html#job) and instance groups."); + editor.assertHoverContains("properties", 3, "Describes global properties. Deprecated"); + editor.assertHoverContains("variables", "Describes variables"); + editor.assertHoverContains("tags", "Specifies key value pairs to be sent to the CPI for VM tagging"); + } + + //@Ignore //For now... because not passing yet. + @Test public void reconcileCfManifest() throws Exception { + Editor editor = harness.newEditorFromClasspath("/workspace/cf-deployment-manifest.yml"); + cloudConfigProvider.readWith(() -> { + throw new IOException("Couldn't contact the director"); + }); + editor.assertProblems(/*NONE*/); + } + + @Test public void toplevelPropertyCompletions() throws Exception { + Editor editor = harness.newEditor( + "<*>" + ); + editor.assertCompletions( + "name: <*>" + ); + + editor = harness.newEditor( + "name: blah\n" + + "<*>" + ); + + editor.assertCompletions( + "name: blah\n" + + "instance_groups:\n" + + "- name: <*>" + , // ============ + "name: blah\n" + + "releases:\n" + + "- name: <*>" + , // ============ + "name: blah\n" + + "stemcells:\n- <*>" + , // ============ + "name: blah\n" + + "tags:\n <*>" + , // ============ + "name: blah\n" + + "update:\n <*>" + , // ============ + "name: blah\n" + + "variables:\n" + + "- name: <*>" +// Below completions are suppressed because they are deprecated +// , // ============ +// "name: blah\n" + +// "director_uuid: <*>" +// , // ============ +// "name: blah\n" + +// "properties:\n <*>" + ); + } + + @Test public void stemcellCompletions() throws Exception { + Editor editor = harness.newEditor( + "stemcells:\n" + + "- <*>" + ); + editor.assertCompletions( + "stemcells:\n" + + "- alias: <*>" + , // ========== + "stemcells:\n" + + "- name: <*>" + , // ========== + "stemcells:\n" + + "- os: <*>" + , // ========== + "stemcells:\n" + + "- version: <*>" + ); + + editor = harness.newEditor( + "stemcells:\n" + + "- alias<*>" + ); + } + + @Test public void stemcellReconciling() throws Exception { + Editor editor = harness.newEditor( + "stemcells:\n" + + "- {}" + ); + editor.assertProblems( + "-|One of [name, os] is required", + "-|[alias, version] are required", + "}|[instance_groups, name, releases, update] are required" + ); + } + + @Test public void releasesBlockCompletions() throws Exception { + Editor editor = harness.newEditor( + "releases:\n" + + "- <*>" + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + editor.assertCompletions( + "releases:\n" + + "- name: <*>" + ); + + editor = harness.newEditor( + "releases:\n" + + "- name: foo\n" + + " <*>" + ); + editor.assertCompletions(PLAIN_COMPLETION, + "releases:\n" + + "- name: foo\n" + + " sha1: <*>" + , // ============ + "releases:\n" + + "- name: foo\n" + + " url: <*>" + , // ============ + "releases:\n" + + "- name: foo\n" + + " version: <*>" + ); + } + + @Test public void releasesAdvancedValidations() throws Exception { + Editor editor = harness.newEditor( + "releases:\n" + + "- name: some-release\n" + + " url: https://my.releases.com/funky.tar.gz\n" + + "- name: other-relase\n" + + " url: file:///root/releases/a-nice-file.tar.gz\n" + + "- name: bad-url\n" + + " url: proto://something.com\n" + + "#x" + ); + editor.assertProblems( + "url|'sha1' is required when the 'url' is http(s)", + "proto|Url scheme must be one of [http, https, file]", + "x|are required" + ); + } + + @Test public void releasesBlockPropertyReconcileAndHovers() throws Exception { + Editor editor = harness.newEditor( + "releases:\n" + + "- name: some-release\n" + + " version: some-version\n" + + " url: https://my.releases.com/funky.tar.gz\n" + + " sha1: 440248a31253296b1626ad52886e58900730f32e\n" + + " woot: dunno\n" + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + editor.assertProblems( + "woot|Unknown property" + ); + + editor.assertHoverContains("name", "Name of a release used in the deployment"); + editor.assertHoverContains("version", "The version of the release to use"); + editor.assertHoverContains("url", "URL of the release to use"); + editor.assertHoverContains("sha1", "The SHA1 of the release tarball"); + + editor = harness.newEditor( + "releases:\n" + + "- name: some-release\n" + + " version: <*>\n" + ); + editor.assertCompletionLabels("latest"); + } + + @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" + + " - <*>" + , // ============= + "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: <*>" + +// , // ============= +// Not suggested because its deprecated: +// "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"); + } + + @Test public void instanceGroups_job_hovers() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups:\n" + + "- name: foo\n" + + " jobs:\n" + + " - name: the-job\n" + + " release: the-jobs-release\n" + + " properties:\n" + + " blah: blah\n" + + " consumes:\n" + + " blah: blah \n" + + " provides:\n" + + " blah: blah\n" + ); + editor.assertHoverContains("name", 3, "The job name"); + editor.assertHoverContains("release", "The release where the job exists"); + editor.assertHoverContains("consumes", "Links consumed by the job"); + editor.assertHoverContains("provides", "Links provided by the job"); + editor.assertHoverContains("properties", "Specifies job properties"); + } + + @Test public void instanceGroups_network_hovers() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups:\n" + + "- name: foo\n" + + " networks:\n" + + " - name: the-network\n" + + " static_ips: []\n" + + " default: []\n" + ); + editor.assertHoverContains("name", 3, "A valid network name from the cloud config"); + editor.assertHoverContains("static_ips", "Array of IP addresses"); + editor.assertHoverContains("default", "Specifies which network components"); + } + + @Test public void instanceGroups_env_hovers() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups:\n" + + "- name: foo\n" + + " env:\n" + + " bosh: {}\n" + + " password: []\n" + ); + editor.assertHoverContains("bosh", "no description"); + editor.assertHoverContains("password", "Crypted password"); + } + + @Test public void updateBlockCompletions() throws Exception { + Editor editor = harness.newEditor( + "update:\n" + + " <*>" + ); + editor.assertCompletions(PLAIN_COMPLETION, + "update:\n" + + " canaries: <*>" + , // ===== + "update:\n" + + " canary_watch_time: <*>" + , // ===== + "update:\n" + + " max_in_flight: <*>" + , // ===== + "update:\n" + + " serial: <*>" + , // ===== + "update:\n" + + " update_watch_time: <*>" + ); + } + + @Test public void updateBlockHovers() throws Exception { + Editor editor = harness.newEditor( + "update:\n" + + " canaries: 1\n" + + " max_in_flight: 10\n" + + " canary_watch_time: 1000-30000\n" + + " update_watch_time: 1000-30000\n" + + " serial: false" + ); + editor.assertHoverContains("canaries", "The number of [canary]"); + editor.assertHoverContains("max_in_flight", "maximum number of non-canary instances"); + editor.assertHoverContains("canary_watch_time", "checks whether the canary instances"); + editor.assertHoverContains("update_watch_time", "checks whether the instances"); + editor.assertHoverContains("serial", "deployed in parallel"); + } + + @Test public void variablesBlockCompletions() throws Exception { + Editor editor = harness.newEditor( + "variables:\n" + + "- <*>" + ); + editor.assertCompletions( + "variables:\n" + + "- name: <*>" + ); + + editor = harness.newEditor( + "variables:\n" + + "- name: foo\n" + + " <*>" + ); + editor.assertCompletions(PLAIN_COMPLETION, + "variables:\n" + + "- name: foo\n" + + " options:\n" + + " <*>" + , // =============== + "variables:\n" + + "- name: foo\n" + + " type: <*>" + ); + + editor = harness.newEditor( + "variables:\n" + + "- name: foo\n" + + " type: <*>" + ); + editor.assertCompletionLabels("certificate", "password", "rsa", "ssh"); + } + + @Test public void variablesBlockHovers() throws Exception { + Editor editor = harness.newEditor( + "variables:\n" + + "- name: admin_password\n" + + " type: password\n" + + "- name: default_ca\n" + + " type: certificate\n" + + " options:\n" + + " is_ca: true\n" + + " common_name: some-ca\n" + + "- name: director_ssl\n" + + " type: certificate\n" + + " options:\n" + + " ca: default_ca\n" + + " common_name: cc.cf.internal\n" + + " alternative_names: [cc.cf.internal]" + ); + editor.assertHoverContains("name", "Unique name used to identify a variable"); + 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*/); + + editor = harness.newEditor( + "name: foo\n" + + "director_uuid: dca5480a-6b0e-11e7-907b-a6006ad3dba0\n" + + "networks: {}" //This makes it a V1 schema + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + editor.assertProblems( + "director_uuid|bosh v2 CLI no longer checks or requires", + "networks|Deprecated: 'networks' is a V1 schema property" + ); + } + + @Test public void documentSymbols() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "variables:\n" + + "- name: blobstore_admin_users_password\n" + + " type: password\n" + + "- name: blobstore_secure_link_secret\n" + + " type: password\n" + + "stemcells:\n" + + "- alias: default\n" + + " os: ubuntu-trusty\n" + + " version: '3421.11'\n" + + "instance_groups:\n" + + "- name: foo-group\n" + + " networks:\n" + + " - name: the-network\n" + + " static_ips: []\n" + + " default: []\n" + + "- name: bar-group\n" + + " networks:\n" + + " - name: the-network\n" + + " static_ips: []\n" + + " default: []\n" + + "releases:\n" + + "- name: one-release\n" + + "- name: other-release\n" + ); + editor.assertDocumentSymbols( + "default|StemcellAlias", + "foo-group|InstanceGroup", + "bar-group|InstanceGroup", + "one-release|Release", + "other-release|Release", + "blobstore_admin_users_password|Variable", + "blobstore_secure_link_secret|Variable" + ); + } + + @Test public void duplicateSymbolChecking() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "variables:\n" + + "- name: dup_var\n" + + " type: password\n" + + "- name: blobstore_admin_users_password\n" + + " type: password\n" + + "- name: blobstore_secure_link_secret\n" + + " type: password\n" + + "- name: dup_var\n" + + " type: ssh\n" + + "stemcells:\n" + + "- alias: default\n" + + " os: ubuntu-trusty\n" + + " version: '3421.11'\n" + + "- alias: dup_cell\n" + + " os: ubuntu-trusty\n" + + " version: '3421.11'\n" + + "- alias: dup_cell\n" + + " os: ubuntu-trusty\n" + + " version: '3421.11'\n" + + "instance_groups:\n" + + "- name: foo-group\n" + + " networks:\n" + + " - name: the-network\n" + + " static_ips: []\n" + + " default: []\n" + + "- name: bar-group\n" + + " networks:\n" + + " - name: the-network\n" + + " static_ips: []\n" + + " default: []\n" + + "- name: bar-group\n" + + " networks:\n" + + " - name: the-network\n" + + " static_ips: []\n" + + " default: []\n" + + "releases:\n" + + "- name: one-release\n" + + "- name: other-release\n" + + "- name: one-release\n" + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + + editor.assertProblems( + "dup_var|Duplicate 'VariableName'", + "dup_var|Duplicate 'VariableName'", + "dup_cell|Duplicate 'StemcellAlias'", + "dup_cell|Duplicate 'StemcellAlias'", + "bar-group|Duplicate 'InstanceGroupName'", + "bar-group|Duplicate 'InstanceGroupName'", + "one-release|Duplicate 'ReleaseName'", + "one-release|Duplicate 'ReleaseName'" + ); + } + + @Test public void contentAssistReleaseReference() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups: \n" + + "- name: some-server\n" + + " jobs:\n" + + " - release: <*>\n" + + "releases: \n" + + "- name: some-release\n" + + " url: https://release-hub.info/some-release.tar.gz?version=99.3.2\n" + + " sha1: asddsfsd\n" + + "- name: other-release\n" + + " url: https://release-hub.info/other-release.tar.gz?version=99.3.2\n" + + " sha1: asddsfsd\n" + ); + editor.assertContextualCompletions("<*>" + , // ==> + "other-release<*>", "some-release<*>" + ); + } + + @Test public void reconcileReleaseReference() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups: \n" + + "- name: some-server\n" + + " jobs:\n" + + " - release: some-release\n" + + "- name: some-other-server\n" + + " jobs:\n" + + " - release: bogus-release\n" + + "releases: \n" + + "- name: some-release\n" + + " url: https://release-hub.info/some-release.tar.gz?version=99.3.2\n" + + " sha1: asddsfsd\n" + + "- name: other-release\n" + + " url: https://release-hub.info/other-release.tar.gz?version=99.3.2\n" + + " sha1: asddsfsd\n" + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + editor.assertProblems( + "bogus-release|unknown 'ReleaseName'. Valid values are: [other-release, some-release]" + ); + } + + @Test public void contentAssistStemcellReference() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups: \n" + + "- name: some-server\n" + + " stemcell: <*>\n" + + "stemcells:\n" + + "- alias: default\n" + + " os: ubuntu\n" + + " version: 1346.77.1\n" + + "- alias: windoze\n" + + " os: windows\n" + + " version: 678.9.1\n" + ); + editor.assertContextualCompletions("<*>" + , // ==> + "default<*>", "windoze<*>" + ); + } + + @Test public void reconcileStemcellReference() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups: \n" + + "- name: some-server\n" + + " stemcell: default\n" + + "- name: windoze-server\n" + + " stemcell: windoze\n" + + "- name: bad-server\n" + + " stemcell: bogus-stemcell\n" + + "stemcells:\n" + + "- alias: default\n" + + " os: ubuntu\n" + + " version: 1346.77.1\n" + + "- alias: windoze\n" + + " os: windows\n" + + " version: 678.9.1\n" + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + editor.assertProblems( + "bogus-stemcell|unknown 'StemcellAlias'. Valid values are: [default, windoze]" + ); + } + + @Test public void contentAssistVMtype() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups: \n" + + "- name: some-server\n" + + " stemcell: windoze\n" + + " vm_type: <*>" + ); + editor.assertContextualCompletions( + "<*>" + , // ==> + "default<*>", + "large<*>" + ); + + //Verify that the cache is working. Shouldn't read the cc provier more than once, evem for multiple CA requests. + editor.assertCompletionLabels("default", "large"); + editor.assertCompletionLabels("default", "large"); + assertEquals(1, cloudConfigProvider.getReadCount()); + } + + @Test public void reconcileVMtype() throws Exception { + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups: \n" + + "- name: some-server\n" + + " vm_type: bogus-vm\n" + + "- name: other-server\n" + + " vm_type: large" + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + editor.assertProblems( + "bogus-vm|unknown 'VMType'. Valid values are: [default, large]" + ); + assertEquals(1, cloudConfigProvider.getReadCount()); + } + + @Test public void reconcileVMTypeWhenCloudConfigUnavailable() throws Exception { + cloudConfigProvider.readWith(() -> null); + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups: \n" + + "- name: some-server\n" + + " vm_type: bogus-vm\n" + + "- name: other-server\n" + + " vm_type: large" + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + editor.assertProblems(/*NONE*/); //Should not complain about unknown vm_types, if we can't determine what valid vm_types actually exist. + } + + @Test public void reconcileVMTypeWhenCloudConfigThrows() throws Exception { + cloudConfigProvider.readWith(() -> { throw new TimeoutException("Reading cloud config timed out"); }); + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups: \n" + + "- name: some-server\n" + + " vm_type: bogus-vm\n" + + "- name: other-server\n" + + " vm_type: large" + ); + editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); + editor.assertProblems(/*NONE*/); //Should not complain about unknown vm_types, if we can't determine what valid vm_types actually exist. + } + + @Test public void contentAssistShowsWarningWhenCloudConfigThrows() throws Exception { + cloudConfigProvider.readWith(() -> { + throw new TimeoutException("Reading cloud config timed out"); + }); + Editor editor = harness.newEditor( + "name: foo\n" + + "instance_groups: \n" + + "- name: some-server\n" + + " vm_type: <*>" + ); + CompletionItem completion = editor.assertCompletionLabels("TimeoutException").get(0); + completion = harness.resolveCompletionItem(completion); + assertContains("Reading cloud config timed out", completion.getDocumentation()); + } + +} diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshLanguageServerTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshLanguageServerTest.java similarity index 88% rename from headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshLanguageServerTest.java rename to headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshLanguageServerTest.java index 3b542dd30..c3eb682f1 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshLanguageServerTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshLanguageServerTest.java @@ -8,8 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ - -package org.springframework.ide.vscode.concourse; +package org.springframework.ide.vscode.bosh; import static org.assertj.core.api.Assertions.assertThat; @@ -20,7 +19,7 @@ import java.nio.file.Paths; import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.junit.Test; -import org.springframework.ide.vscode.bosh.BoshLanguageServer; +import org.springframework.ide.vscode.bosh.mocks.MockCloudConfigProvider; import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; public class BoshLanguageServerTest { @@ -31,7 +30,9 @@ public class BoshLanguageServerTest { @Test public void createAndInitializeServerWithWorkspace() throws Exception { - LanguageServerHarness harness = new LanguageServerHarness(() -> new BoshLanguageServer()); + LanguageServerHarness harness = new LanguageServerHarness(() -> + new BoshLanguageServer(new MockCloudConfigProvider()) + ); File workspaceRoot = getTestResource("/workspace/"); assertExpectedInitResult(harness.intialize(workspaceRoot)); } @@ -39,7 +40,9 @@ public class BoshLanguageServerTest { @Test public void createAndInitializeServerWithoutWorkspace() throws Exception { File workspaceRoot = null; - LanguageServerHarness harness = new LanguageServerHarness(() -> new BoshLanguageServer()); + LanguageServerHarness harness = new LanguageServerHarness(() -> + new BoshLanguageServer(new MockCloudConfigProvider()) + ); assertExpectedInitResult(harness.intialize(workspaceRoot)); } diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshValueParserTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshValueParserTest.java new file mode 100644 index 000000000..c54f6ea2a --- /dev/null +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshValueParserTest.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * 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.bosh; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.ide.vscode.bosh.BoshValueParsers; +import org.springframework.ide.vscode.commons.util.ValueParseException; +import org.springframework.ide.vscode.commons.util.ValueParser; + +public class BoshValueParserTest { + + private static final String MARKER = "<*>"; + + @Test public void integerOrRangeOkay() throws Exception { + BoshValueParsers.INTEGER_OR_RANGE.parse("123"); + BoshValueParsers.INTEGER_OR_RANGE.parse("123-456"); + } + + @Test public void integerOrRangeGarbage() throws Exception { + assertProblem(BoshValueParsers.INTEGER_OR_RANGE, "<*>garbage<*>", "Should be either a Integer, or a range (of the form '-')"); + assertProblem(BoshValueParsers.INTEGER_OR_RANGE, "<*>123--456<*>", "Should be either a Integer, or a range (of the form '-')"); + assertProblem(BoshValueParsers.INTEGER_OR_RANGE, "<*>garbage<*>-123", "Should be a Integer"); + assertProblem(BoshValueParsers.INTEGER_OR_RANGE, "123-<*>garbage<*>", "Should be a Integer"); + assertProblem(BoshValueParsers.INTEGER_OR_RANGE, "<*>123-122<*>", "123 should be smaller than 122"); + } + + @Test public void urlOkay() throws Exception { + ValueParser urlParser = BoshValueParsers.url("http", "https", "file"); + urlParser.parse("http://foobar.com/munhings.tar.gz"); + urlParser.parse("https://foobar.com/munhings.tar.gz"); + urlParser.parse("hTTp://foobar.com/munhings.tar.gz"); + urlParser.parse("HTTPS://foobar.com/munhings.tar.gz"); + urlParser.parse("file://local/file"); + urlParser.parse("file:///local/file"); + urlParser.parse("FILE:///local/file"); + } + + @Test public void urlGarbage() throws Exception { + ValueParser urlParser = BoshValueParsers.url("http", "https", "file"); + assertProblem(urlParser, "<*>woot<*>://foobar.com", "Url scheme must be one of [http, https, file]"); + assertProblem(urlParser, "<*>wOOt<*>://foobar.com", "Url scheme must be one of [http, https, file]"); + } + + private void assertProblem(ValueParser parser, String input, String expectedMessage) throws Exception { + String unmarkedInput = input.replace(MARKER, ""); + int firstMarker = input.indexOf(MARKER); + assertTrue(firstMarker>=0); + int secondMarker = input.indexOf(MARKER, firstMarker+1)-MARKER.length(); + assertTrue(secondMarker>=firstMarker); + try { + parser.parse(unmarkedInput); + } catch (ValueParseException e) { + int startIndex = startIndex(unmarkedInput, e); + int endIndex = endIndex(unmarkedInput, e); + String markedInput = + unmarkedInput.substring(0, startIndex) + + MARKER + + unmarkedInput.substring(startIndex, endIndex) + + MARKER + + unmarkedInput.substring(endIndex); + assertEquals(input, markedInput); + assertEquals(expectedMessage, e.getMessage()); + } + } + + private int endIndex(String unmarkedInput, ValueParseException e) { + int i = e.getEndIndex(); + if (i>=0) { + return i; + } else { + return unmarkedInput.length(); + } + } + + private int startIndex(String unarkedInput, ValueParseException e) { + int i = e.getStartIndex(); + if (i>=0) { + return i; + } else { + return 0; + } + } + +} diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/mocks/MockCloudConfigProvider.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/mocks/MockCloudConfigProvider.java new file mode 100644 index 000000000..542cf4fdf --- /dev/null +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/mocks/MockCloudConfigProvider.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * 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.bosh.mocks; + +import java.util.concurrent.Callable; + +import org.springframework.ide.vscode.bosh.BoshCommandCloudConfigProviderTest; +import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider; +import org.springframework.ide.vscode.commons.util.IOUtil; + +public final class MockCloudConfigProvider extends BoshCommandCloudConfigProvider { + + static final String MOCK_DATA_RSRC = "/cmd-out/cloud-config.json"; + + private Callable cloudConfigReader = () -> IOUtil.toString(BoshCommandCloudConfigProviderTest.class.getResourceAsStream(MOCK_DATA_RSRC)); + + private int readCount = 0; + + /** + * Override with a 'fake' which just returns some mock data. That way we can unit-test + * without requiring a real bosh setup. + */ + @Override + protected String executeBoshCloudConfigCommand() throws Exception { + readCount++; + return cloudConfigReader.call(); + } + + public MockCloudConfigProvider readWith(Callable reader) { + this.cloudConfigReader = reader; + return this; + } + + public int getReadCount() { + return readCount; + } +} \ 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 deleted file mode 100644 index 713bae766..000000000 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/concourse/BoshEditorTest.java +++ /dev/null @@ -1,247 +0,0 @@ -/******************************************************************************* - * 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.concourse; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.ide.vscode.bosh.BoshLanguageServer; -import org.springframework.ide.vscode.commons.util.text.LanguageId; -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; - - @Before public void setup() throws Exception { - harness = new LanguageServerHarness(() -> { - return new BoshLanguageServer() - .setMaxCompletions(100); - }, - LanguageId.BOSH_DEPLOYMENT - ); - harness.intialize(null); - } - - @Test public void toplevelV2PropertyNamesKnown() throws Exception { - Editor editor = harness.newEditor( - "name: some-name\n" + - "director_uuid: cf8dc1fc-9c42-4ffc-96f1-fbad983a6ce6\n" + - "releases:\n" + - "- name: redis\n" + - " version: 12\n" + - "stemcells:\n" + - "- alias: default\n" + - " os: ubuntu-trusty\n" + - " version: 3421.11\n" + - "update:\n" + - " canaries: 1\n" + - " max_in_flight: 10\n" + - " canary_watch_time: 1000-30000\n" + - " update_watch_time: 1000-30000\n" + - "instance_groups:\n" + - "- name: redis-master\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" + - "- name: redis-slave\n" + - " instances: 2\n" + - " azs: [z1, z2]\n" + - " jobs:\n" + - " - name: redis-server\n" + - " release: redis\n" + - " properties: {}\n" + - " vm_type: medium\n" + - " stemcell: default\n" + - " persistent_disk_type: medium\n" + - " networks:\n" + - " - name: default\n" + - "variables:\n" + - "- name: admin_password\n" + - " type: password\n" + - "- name: default_ca\n" + - " type: certificate\n" + - " options:\n" + - " is_ca: true\n" + - " common_name: some-ca\n" + - "- name: director_ssl\n" + - " type: certificate\n" + - " options:\n" + - " ca: default_ca\n" + - " common_name: cc.cf.internal\n" + - " alternative_names: [cc.cf.internal]\n" + - "properties:\n" + - " a-property: the-value\n" + - "tags:\n" + - " project: cf\n" + - "blah: hoooo\n" - ); - editor.assertProblems( - "properties|Deprecated in favor of job level properties and links", - "blah|Unknown property" - ); - - editor.assertHoverContains("name", "The name of the deployment"); - editor.assertHoverContains("director_uuid", "This string must match the UUID of the currently targeted Director"); - editor.assertHoverContains("releases", "The name and version of each release in the deployment"); - editor.assertHoverContains("stemcells", "The name and version of each stemcell"); - editor.assertHoverContains("update", "This specifies instance update properties"); - editor.assertHoverContains("instance_groups", "Specifies the mapping between release [jobs](https://bosh.io/docs/terminology.html#job) and instance groups."); - editor.assertHoverContains("properties", 3, "Describes global properties. Deprecated"); - editor.assertHoverContains("variables", "Describes variables"); - editor.assertHoverContains("tags", "Specifies key value pairs to be sent to the CPI for VM tagging"); - } - - @Test public void toplevelV2PropertyCompletions() throws Exception { - Editor editor = harness.newEditor( - "<*>" - ); - editor.assertCompletions( - "name: <*>" - ); - - editor = harness.newEditor( - "name: blah\n" + - "<*>" - ); - - editor.assertCompletions( - "name: blah\n" + - "director_uuid: <*>" - , // ============ - "name: blah\n" + - "instance_groups:\n- <*>" - , // ============ - "name: blah\n" + - "releases:\n- <*>" - , // ============ - "name: blah\n" + - "stemcells:\n- <*>" - , // ============ - "name: blah\n" + - "tags:\n <*>" - , // ============ - "name: blah\n" + - "update:\n <*>" - , // ============ - "name: blah\n" + - "variables:\n- <*>" - , // ============ - "name: blah\n" + - "properties:\n <*>" - ); - } - - @Test public void stemcellCompletions() throws Exception { - Editor editor = harness.newEditor( - "stemcells:\n" + - "- <*>" - ); - editor.assertCompletions( - "stemcells:\n" + - "- alias: <*>" - , // ========== - "stemcells:\n" + - "- name: <*>" - , // ========== - "stemcells:\n" + - "- os: <*>" - , // ========== - "stemcells:\n" + - "- version: <*>" - ); - - editor = harness.newEditor( - "stemcells:\n" + - "- alias<*>" - ); - } - - @Test public void stemcellReconciling() throws Exception { - Editor editor = harness.newEditor( - "stemcells:\n" + - "- {}" - ); - editor.assertProblems( - "-|One of [name, os] is required", - "-|[alias, version] are required", - "}|[director_uuid, instance_groups, name, releases, update] are required" - ); - } - - @Test public void releasesBlockCompletions() throws Exception { - Editor editor = harness.newEditor( - "releases:\n" + - "- <*>" - ); - editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); - editor.assertCompletions( - "releases:\n" + - "- name: <*>" - ); - - editor = harness.newEditor( - "releases:\n" + - "- name: foo\n" + - " <*>" - ); - editor.assertCompletions(PLAIN_COMPLETION, - "releases:\n" + - "- name: foo\n" + - " version: <*>" - ); - } - - @Test public void releasesBlockReconcileAndHovers() throws Exception { - Editor editor = harness.newEditor( - "releases:\n" + - "- name: some-release\n" + - " version: some-version\n" + - " woot: dunno\n" - ); - editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY); - editor.assertProblems( - "woot|Unknown property" - ); - - editor.assertHoverContains("name", "Name of a release used in the deployment"); - editor.assertHoverContains("version", "The version of the release to use"); - - editor = harness.newEditor( - "releases:\n" + - "- name: some-release\n" + - " version: <*>\n" - ); - editor.assertCompletionLabels("latest"); - } - - @Test public void instanceGroupsCompletions() throws Exception { - Editor editor = harness.newEditor( - "instance_groups:\n" + - "- <*>" - ); - - } - -} diff --git a/headless-services/bosh-language-server/src/test/resources/cmd-out/cloud-config.json b/headless-services/bosh-language-server/src/test/resources/cmd-out/cloud-config.json new file mode 100644 index 000000000..360653a13 --- /dev/null +++ b/headless-services/bosh-language-server/src/test/resources/cmd-out/cloud-config.json @@ -0,0 +1,10 @@ +{ + "Tables": null, + "Blocks": [ + "azs:\n- cloud_properties:\n datacenters:\n - clusters:\n - AppFabric: {}\n name: z1\n- cloud_properties:\n datacenters:\n - clusters:\n - AppFabric: {}\n name: z2\n- cloud_properties:\n datacenters:\n - clusters:\n - AppFabric: {}\n name: z3\ncompilation:\n az: z1\n network: default\n reuse_compilation_vms: true\n vm_type: default\n workers: 5\ndisk_types:\n- disk_size: 3000\n name: default\n- disk_size: 50000\n name: large\nnetworks:\n- name: default\n subnets:\n - azs:\n - z1\n - z2\n - z3\n cloud_properties:\n name: VLAN 40 - AF\n dns:\n - 10.192.2.10\n - 8.8.8.8\n gateway: 10.194.4.1\n range: 10.194.4.0/23\n reserved:\n - 10.194.4.1-10.194.4.34\n - 10.194.4.40-10.194.5.255\n type: manual\nvm_types:\n- cloud_properties:\n cpu: 2\n disk: 3240\n ram: 1024\n name: default\n- cloud_properties:\n cpu: 2\n disk: 30240\n ram: 4096\n name: large\n" + ], + "Lines": [ + "Using environment '10.194.4.35' as client 'admin'", + "Succeeded" + ] +} \ No newline at end of file diff --git a/headless-services/bosh-language-server/src/test/resources/workspace/another-pipeline.yml b/headless-services/bosh-language-server/src/test/resources/workspace/another-pipeline.yml deleted file mode 100644 index e69de29bb..000000000 diff --git a/headless-services/bosh-language-server/src/test/resources/workspace/cf-deployment-manifest.yml b/headless-services/bosh-language-server/src/test/resources/workspace/cf-deployment-manifest.yml new file mode 100644 index 000000000..5576ff88f --- /dev/null +++ b/headless-services/bosh-language-server/src/test/resources/workspace/cf-deployment-manifest.yml @@ -0,0 +1,1728 @@ +--- +name: cf +update: + canaries: 1 + canary_watch_time: 30000-1200000 + max_in_flight: 5 + serial: false + update_watch_time: 5000-1200000 +instance_groups: +- name: smoke-tests + lifecycle: errand + azs: + - z1 + instances: 1 + vm_type: m3.medium + stemcell: default + update: + max_in_flight: 1 + serial: true + networks: + - name: default + jobs: + - name: smoke_tests + release: cf-smoke-tests + properties: + smoke_tests: + api: "https://api.((system_domain))" + apps_domain: "((system_domain))" + user: admin + password: "((cf_admin_password))" + org: cf_smoke_tests_org + space: cf_smoke_tests_space + cf_dial_timeout_in_seconds: 300 + skip_ssl_validation: true +- name: consul + azs: + - z1 + - z2 + - z3 + instances: 3 + persistent_disk_type: 5GB + vm_type: m3.medium + stemcell: default + update: + max_in_flight: 1 + serial: true + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: {from: consul_server_link} + consul_client: {from: consul_client_link} + provides: + consul_common: {as: consul_common_link, shared: true} + consul_server: {as: consul_server_link, shared: true} + consul_client: {as: consul_client_link, shared: true} + properties: + consul: + agent: + mode: server + domain: cf.internal + encrypt_keys: + - "((consul_encrypt_key))" + agent_cert: "((consul_agent.certificate))" + agent_key: "((consul_agent.private_key))" + ca_cert: "((consul_agent.ca))" + server_cert: "((consul_server.certificate))" + server_key: "((consul_server.private_key))" + - name: metron_agent + release: loggregator + properties: &metron_agent_properties + metron_agent: + deployment: "((system_domain))" + metron_endpoint: + shared_secret: "((dropsonde_shared_secret))" + loggregator: + tls: + ca_cert: "((loggregator_tls_metron.ca))" + metron: + cert: "((loggregator_tls_metron.certificate))" + key: "((loggregator_tls_metron.private_key))" +- name: nats + azs: + - z1 + - z2 + instances: 2 + vm_type: c3.large + vm_extensions: + - 10GB_ephemeral_disk + stemcell: default + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + - name: nats + release: nats + provides: + nats: {as: nats, shared: true} + properties: + nats: + password: "((nats_password))" + user: nats + monitor_port: 9222 + prof_port: 6060 + - name: metron_agent + release: loggregator + properties: *metron_agent_properties +- name: etcd + azs: + - z1 + - z2 + - z3 + instances: 3 + persistent_disk_type: 5GB + vm_type: m3.medium + vm_extensions: + - 10GB_ephemeral_disk + stemcell: default + update: + serial: true + max_in_flight: 1 + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + properties: + consul: + agent: + services: + etcd: + name: cf-etcd + - name: etcd + release: etcd + consumes: + etcd: nil + properties: + etcd: + advertise_urls_dns_suffix: cf-etcd.service.cf.internal + cluster: + - instances: 3 + name: etcd + peer_require_ssl: true + require_ssl: true + ca_cert: "((etcd_client.ca))" + client_cert: "((etcd_client.certificate))" + client_key: "((etcd_client.private_key))" + server_cert: "((etcd_server.certificate))" + server_key: "((etcd_server.private_key))" + peer_ca_cert: "((etcd_peer.ca))" + peer_cert: "((etcd_peer.certificate))" + peer_key: "((etcd_peer.private_key))" + - name: etcd_metrics_server + release: etcd + properties: + etcd_metrics_server: + etcd: + dns_suffix: cf-etcd.service.cf.internal + require_ssl: true + ca_cert: "((etcd_server.ca))" + client_cert: "((etcd_client.certificate))" + client_key: "((etcd_client.private_key))" + - name: metron_agent + release: loggregator + properties: *metron_agent_properties +- name: doppler + azs: + - z1 + - z2 + instances: 2 + vm_type: m3.medium + vm_extensions: + - 10GB_ephemeral_disk + stemcell: default + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + properties: + consul: + agent: + services: + doppler: + name: doppler + - name: doppler + release: loggregator + properties: + doppler: + etcd: + client_cert: "((etcd_client.certificate))" + client_key: "((etcd_client.private_key))" + loggregator: + tls: + ca_cert: "((loggregator_tls_doppler.ca))" + doppler: + cert: "((loggregator_tls_doppler.certificate))" + key: "((loggregator_tls_doppler.private_key))" + etcd: + require_ssl: true + ca_cert: "((etcd_server.ca))" + machines: + - cf-etcd.service.cf.internal + doppler_endpoint: + shared_secret: "((dropsonde_shared_secret))" + - name: syslog_drain_binder + release: loggregator + properties: + loggregator: + tls: + syslogdrainbinder: + cert: "((loggregator_tls_syslogdrainbinder.certificate))" + key: "((loggregator_tls_syslogdrainbinder.private_key))" + etcd: + require_ssl: true + ca_cert: "((etcd_server.ca))" + machines: + - cf-etcd.service.cf.internal + syslog_drain_binder: + etcd: + client_cert: "((etcd_client.certificate))" + client_key: "((etcd_client.private_key))" + system_domain: "((system_domain))" + cc: + mutual_tls: + ca_cert: "((loggregator_tls_syslogdrainbinder.ca))" + srv_api_uri: https://api.((system_domain)) + ssl: &ssl + skip_cert_verify: true + - name: metron_agent + release: loggregator + properties: *metron_agent_properties +- name: mysql + azs: + - z1 + persistent_disk_type: 10GB + instances: 1 + vm_type: m3.large + vm_extensions: + - 10GB_ephemeral_disk + stemcell: default + update: + serial: true + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + - name: mysql + release: cf-mysql + properties: + network_name: default + cf_mysql: + mysql: + admin_password: "((cf_mysql_mysql_admin_password))" + port: 33306 + binlog_enabled: false + cluster_health: + password: "((cf_mysql_mysql_cluster_health_password))" + galera_healthcheck: + db_password: "((cf_mysql_mysql_galera_healthcheck_password))" + endpoint_username: galera_healthcheck + endpoint_password: "((cf_mysql_mysql_galera_healthcheck_endpoint_password))" + seeded_databases: + - name: cloud_controller + username: cloud_controller + password: "((cc_database_password))" + - name: diego + username: diego + password: "((diego_database_password))" + - name: network_connectivity + username: network_connectivity + password: "((network_connectivity_database_password))" + - name: network_policy + username: network_policy + password: "((network_policy_database_password))" + - name: routing-api + username: routing-api + password: "((routing_api_database_password))" + - name: uaa + username: uaa + password: "((uaa_database_password))" + - name: proxy + release: cf-mysql + properties: + cf_mysql: + external_host: "((system_domain))" + proxy: + api_password: "((cf_mysql_proxy_api_password))" + consul_enabled: true + consul_service_name: "sql-db" + - name: metron_agent + release: loggregator + properties: *metron_agent_properties +- name: diego-api + migrated_from: + - name: diego-bbs + azs: + - z1 + - z2 + instances: 2 + vm_type: m3.large + stemcell: default + update: + serial: true + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + properties: + consul: + agent: + services: + silk-controller: + name: silk-controller + - name: cfdot + release: diego + properties: + diego: + cfdot: + bbs: &diego_bbs_client_properties + ca_cert: "((diego_bbs_client.ca))" + client_cert: "((diego_bbs_client.certificate))" + client_key: "((diego_bbs_client.private_key))" + - name: bbs + release: diego + properties: + diego: + bbs: + active_key_label: key-2016-06 + encryption_keys: + - label: key-2016-06 + passphrase: "((diego_bbs_encryption_keys_passphrase))" + sql: + db_host: sql-db.service.cf.internal + db_port: 3306 + db_schema: diego + db_username: diego + db_password: "((diego_database_password))" + db_driver: mysql + etcd: + machines: [] + ca_cert: nil + client_cert: nil + client_key: nil + ca_cert: "((diego_bbs_server.ca))" + auctioneer: &diego_auctioneer_client_properties + ca_cert: "((diego_auctioneer_client.ca))" + client_cert: "((diego_auctioneer_client.certificate))" + client_key: "((diego_auctioneer_client.private_key))" + server_cert: "((diego_bbs_server.certificate))" + server_key: "((diego_bbs_server.private_key))" + rep: + require_tls: true + ca_cert: "((diego_rep_client.ca))" + client_cert: "((diego_rep_client.certificate))" + client_key: "((diego_rep_client.private_key))" + - name: metron_agent + release: loggregator + properties: *metron_agent_properties + - name: silk-controller + release: cf-networking + properties: + cf_networking: + silk_controller: + ca_cert: ((silk_controller.ca)) + server_cert: ((silk_controller.certificate)) + server_key: ((silk_controller.private_key)) + database: + type: mysql + username: network_connectivity + password: ((network_connectivity_database_password)) + host: sql-db.service.cf.internal + port: 3306 + name: network_connectivity + silk_daemon: + ca_cert: ((silk_daemon.ca)) + client_cert: ((silk_daemon.certificate)) + client_key: ((silk_daemon.private_key)) +- name: uaa + azs: + - z1 + - z2 + instances: 2 + vm_type: m3.medium + vm_extensions: + - 10GB_ephemeral_disk + stemcell: default + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + properties: + consul: + agent: + services: + uaa: {} + - name: uaa + release: uaa + properties: + login: + saml: + serviceProviderKey: "((uaa_login_saml.private_key))" + serviceProviderKeyPassword: '' + serviceProviderCertificate: "((uaa_login_saml.certificate))" + uaa: + sslCertificate: "((uaa_ssl.certificate))" + sslPrivateKey: "((uaa_ssl.private_key))" + zones: + internal: + hostnames: + - uaa.service.cf.internal + url: https://uaa.((system_domain)) + admin: + client_secret: "((uaa_admin_client_secret))" + scim: + users: + - name: admin + password: "((cf_admin_password))" + groups: + - cloud_controller.admin + - doppler.firehose + - network.admin + - openid + - routing.router_groups.read + - routing.router_groups.write + - scim.read + - scim.write + jwt: + policy: + active_key_id: key-1 + keys: + key-1: + signingKey: "((uaa_jwt_signing_key.private_key))" + clients: + cc-service-dashboards: + authorities: clients.read,clients.write,clients.admin + authorized-grant-types: client_credentials + scope: openid,cloud_controller_service_permissions.read + secret: "((uaa_clients_cc-service-dashboards_secret))" + cc_routing: + authorities: routing.router_groups.read + authorized-grant-types: client_credentials + secret: "((uaa_clients_cc-routing_secret))" + cf: + access-token-validity: 600 + authorities: uaa.none + authorized-grant-types: password,refresh_token + override: true + refresh-token-validity: 2592000 + scope: network.admin,network.write,cloud_controller.read,cloud_controller.write,openid,password.write,cloud_controller.admin,scim.read,scim.write,doppler.firehose,uaa.user,routing.router_groups.read,routing.router_groups.write + secret: '' + cloud_controller_username_lookup: + authorities: scim.userids + authorized-grant-types: client_credentials + secret: "((uaa_clients_cloud_controller_username_lookup_secret))" + doppler: + authorities: uaa.resource + override: true + authorized-grant-types: client_credentials + secret: "((uaa_clients_doppler_secret))" + gorouter: + authorities: routing.routes.read + authorized-grant-types: client_credentials + secret: "((uaa_clients_gorouter_secret))" + ssh-proxy: + authorized-grant-types: authorization_code + autoapprove: true + override: true + redirect-uri: "https://uaa.((system_domain))/login" + scope: openid,cloud_controller.read,cloud_controller.write + secret: "((uaa_clients_ssh-proxy_secret))" + routing_api_client: + authorities: routing.routes.write,routing.routes.read,routing.router_groups.read + authorized-grant-types: client_credentials + secret: "((uaa_clients_routing_api_client_secret))" + network-policy: + authorities: uaa.resource,cloud_controller.admin_read_only + authorized-grant-types: client_credentials + secret: ((uaa_clients_network_policy_secret)) + tcp_emitter: + authorities: routing.routes.write,routing.routes.read,routing.router_groups.read + authorized-grant-types: client_credentials + secret: "((uaa_clients_tcp_emitter_secret))" + uaadb: + databases: + - name: uaa + tag: uaa + db_scheme: mysql + port: 3306 + roles: + - name: uaa + password: "((uaa_database_password))" + tag: admin + - name: route_registrar + release: routing + properties: + route_registrar: + routes: + - health_check: + name: uaa-healthcheck + script_path: "/var/vcap/jobs/uaa/bin/health_check" + name: uaa + port: 8080 + registration_interval: 10s + tags: + component: uaa + uris: + - uaa.((system_domain)) + - "*.uaa.((system_domain))" + - login.((system_domain)) + - "*.login.((system_domain))" + - name: metron_agent + release: loggregator + properties: *metron_agent_properties + - name: statsd_injector + release: statsd-injector + properties: &statsd_injector_properties + statsd_injector: + deployment: "((system_domain))" + loggregator: + tls: + ca_cert: "((loggregator_tls_statsdinjector.ca))" + statsd_injector: + cert: "((loggregator_tls_statsdinjector.certificate))" + key: "((loggregator_tls_statsdinjector.private_key))" +- name: blobstore + azs: + - z1 + instances: 1 + vm_type: m3.large + vm_extensions: + - 10GB_ephemeral_disk + persistent_disk_type: 100GB + stemcell: default + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + properties: + consul: + agent: + services: + blobstore: {} + - name: blobstore + release: capi + properties: + system_domain: "((system_domain))" + blobstore: + admin_users: + - username: blobstore-user + password: "((blobstore_admin_users_password))" + secure_link: + secret: "((blobstore_secure_link_secret))" + tls: + cert: "((blobstore_tls.certificate))" + private_key: "((blobstore_tls.private_key))" + - name: route_registrar + release: routing + properties: + route_registrar: + routes: + - name: blobstore + port: 8080 + registration_interval: 20s + tags: + component: blobstore + uris: + - blobstore.((system_domain)) + - name: metron_agent + release: loggregator + properties: *metron_agent_properties +- name: api + azs: + - z1 + - z2 + instances: 2 + vm_type: m3.large + vm_extensions: + - 50GB_ephemeral_disk + stemcell: default + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + properties: + consul: + agent: + services: + cloud_controller_ng: {} + routing-api: {} + policy-server: + name: policy-server + - name: cloud_controller_ng + release: capi + provides: + cloud_controller: {as: cloud_controller, shared: true} + properties: + router: + route_services_secret: "((router_route_services_secret))" + hm9000: + port: -1 + system_domain: "((system_domain))" + app_domains: &app_domains + - "((system_domain))" + app_ssh: + host_key_fingerprint: "((diego_ssh_proxy_host_key.public_key_fingerprint))" + routing_api: &routing_api + enabled: true + ssl: + skip_cert_verify: true + uaa: + ca_cert: "((uaa_ssl.ca))" + clients: + cc_routing: + secret: "((uaa_clients_cc-routing_secret))" + cloud_controller_username_lookup: + secret: "((uaa_clients_cloud_controller_username_lookup_secret))" + cc-service-dashboards: + secret: "((uaa_clients_cc-service-dashboards_secret))" + url: https://uaa.((system_domain)) + ssl: + port: 8443 + cc: + default_running_security_groups: &cc_default_running_security_groups + - public_networks + - dns + default_staging_security_groups: &cc_default_staging_security_groups + - public_networks + - dns + security_group_definitions: &cc_security_group_definitions + - name: public_networks + rules: + - destination: 0.0.0.0-9.255.255.255 + protocol: all + - destination: 11.0.0.0-169.253.255.255 + protocol: all + - destination: 169.255.0.0-172.15.255.255 + protocol: all + - destination: 172.32.0.0-192.167.255.255 + protocol: all + - destination: 192.169.0.0-255.255.255.255 + protocol: all + - name: dns + rules: + - destination: 0.0.0.0/0 + ports: '53' + protocol: tcp + - destination: 0.0.0.0/0 + ports: '53' + protocol: udp + install_buildpacks: &cc_install_buildpacks + - name: binary_buildpack + package: binary-buildpack + - name: dotnet_core_buildpack + package: dotnet-core-buildpack + - name: go_buildpack + package: go-buildpack + - name: java_buildpack + package: java-buildpack + - name: nodejs_buildpack + package: nodejs-buildpack + - name: php_buildpack + package: php-buildpack + - name: python_buildpack + package: python-buildpack + - name: python_buildpack + package: python-buildpack + - name: ruby_buildpack + package: ruby-buildpack + - name: staticfile_buildpack + package: staticfile-buildpack + default_to_diego_backend: true + db_encryption_key: "((cc_db_encryption_key))" + bulk_api_password: "((cc_bulk_api_password))" + internal_api_password: "((cc_internal_api_password))" + staging_upload_user: staging_user + staging_upload_password: "((cc_staging_upload_password))" + quota_definitions: "a_definitions + default: + memory_limit: 102400 + non_basic_services_allowed: true + total_routes: 1000 + total_services: -1 + total_reserved_route_ports: 100 + buildpacks: &blobstore-properties + blobstore_type: webdav + webdav_config: + ca_cert: "((blobstore_tls.ca))" + blobstore_timeout: 5 + password: "((blobstore_admin_users_password))" + private_endpoint: https://blobstore.service.cf.internal:4443 + public_endpoint: https://blobstore.((system_domain)) + username: blobstore-user + resource_pool: *blobstore-properties + packages: *blobstore-properties + droplets: *blobstore-properties + mutual_tls: &cc_mutual_tls + ca_cert: "((cc_tls.ca))" + public_cert: "((cc_tls.certificate))" + private_key: "((cc_tls.private_key))" + ccdb: &ccdb + databases: + - name: cloud_controller + tag: cc + db_scheme: mysql + port: 3306 + roles: + - name: cloud_controller + password: "((cc_database_password))" + tag: admin + - name: binary-buildpack + release: binary-buildpack + - name: dotnet-core-buildpack + release: dotnet-core-buildpack + - name: go-buildpack + release: go-buildpack + - name: java-buildpack + release: java-buildpack + - name: nodejs-buildpack + release: nodejs-buildpack + - name: php-buildpack + release: php-buildpack + - name: python-buildpack + release: python-buildpack + - name: ruby-buildpack + release: ruby-buildpack + - name: staticfile-buildpack + release: staticfile-buildpack + - name: route_registrar + release: routing + properties: + route_registrar: + routes: + - name: api + registration_interval: 20s + port: 9022 + uris: + - api.((system_domain)) + - name: policy-server + port: 4002 + registration_interval: 20s + uris: + - api.((system_domain))/networking + - name: metron_agent + release: loggregator + properties: *metron_agent_properties + - name: statsd_injector + release: statsd-injector + properties: *statsd_injector_properties + - name: routing-api + release: routing + properties: + routing_api: + system_domain: "((system_domain))" + router_groups: + - name: default-tcp + type: tcp + reservable_ports: 1024-1123 + sqldb: + host: sql-db.service.cf.internal + type: mysql + port: 3306 + schema: routing-api + username: routing-api + password: "((routing_api_database_password))" + uaa: + ca_cert: "((uaa_ca.certificate))" + tls_port: 8443 + - name: policy-server + release: cf-networking + properties: + cf_networking: + policy_server: + uaa_client_secret: ((uaa_clients_network_policy_secret)) + uaa_ca: ((uaa_ssl.ca)) + ca_cert: ((network_policy_server.ca)) + server_cert: ((network_policy_server.certificate)) + server_key: ((network_policy_server.private_key)) + database: + type: mysql + username: network_policy + password: ((network_policy_database_password)) + host: sql-db.service.cf.internal + port: 3306 + name: network_policy +- name: cc-worker + azs: + - z1 + - z2 + instances: 2 + vm_type: m3.medium + vm_extensions: + - 10GB_ephemeral_disk + stemcell: default + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + - name: metron_agent + release: loggregator + properties: *metron_agent_properties + - name: cloud_controller_worker + release: capi + properties: + hm9000: + port: -1 + cc: + db_encryption_key: "((cc_db_encryption_key))" + default_to_diego_backend: true + install_buildpacks: *cc_install_buildpacks + default_running_security_groups: *cc_default_running_security_groups + default_staging_security_groups: *cc_default_staging_security_groups + security_group_definitions: *cc_security_group_definitions + internal_api_password: "((cc_internal_api_password))" + bulk_api_password: "((cc_bulk_api_password))" + quota_definitions: *quota_definitions + staging_upload_user: staging_user + staging_upload_password: "((cc_staging_upload_password))" + resource_pool: *blobstore-properties + packages: *blobstore-properties + droplets: *blobstore-properties + buildpacks: *blobstore-properties + mutual_tls: *cc_mutual_tls + ccdb: *ccdb + system_domain: "((system_domain))" + app_domains: *app_domains + routing_api: *routing_api + ssl: + skip_cert_verify: true + uaa: + ca_cert: "((uaa_ssl.ca))" + clients: + cc-service-dashboards: + secret: "((uaa_clients_cc-service-dashboards_secret))" + cc_routing: + secret: "((uaa_clients_cc-routing_secret))" + + url: https://uaa.((system_domain)) + ssl: + port: 8443 +- name: router + azs: + - z1 + - z2 + instances: 2 + vm_type: m3.medium + vm_extensions: + - cf-router-network-properties + stemcell: default + update: + max_in_flight: 1 + serial: true + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + properties: + consul: + agent: + services: + gorouter: {} + - name: gorouter + release: routing + properties: + router: + ssl_skip_validation: true + enable_ssl: true + ssl_cert: "((router_ssl.certificate))" + ssl_key: "((router_ssl.private_key))" + status: + password: "((router_status_password))" + user: router-status + route_services_secret: "((router_route_services_secret))" + tracing: + enable_zipkin: true + routing_api: + enabled: true + uaa: + clients: + gorouter: + secret: "((uaa_clients_gorouter_secret))" + ca_cert: "((uaa_ssl.ca))" + ssl: + port: 8443 + - name: metron_agent + release: loggregator + properties: *metron_agent_properties +- name: diego-brain + azs: + - z1 + - z2 + instances: 2 + vm_type: m3.medium + vm_extensions: + - diego-ssh-proxy-network-properties + - 10GB_ephemeral_disk + stemcell: default + update: + serial: true + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + - name: cfdot + release: diego + properties: + diego: + cfdot: + bbs: *diego_bbs_client_properties + - name: ssh_proxy + release: diego + properties: + diego: + ssh_proxy: + enable_cf_auth: true + host_key: "((diego_ssh_proxy_host_key.private_key))" + uaa_secret: "((uaa_clients_ssh-proxy_secret))" + uaa: + ca_cert: "((uaa_ssl.ca))" + port: 8443 + bbs: *diego_bbs_client_properties + - name: file_server + release: diego + - name: auctioneer + release: diego + properties: + diego: + auctioneer: + bbs: *diego_bbs_client_properties + ca_cert: "((diego_auctioneer_server.ca))" + server_cert: "((diego_auctioneer_server.certificate))" + server_key: "((diego_auctioneer_server.private_key))" + rep: + require_tls: true + ca_cert: "((diego_rep_client.ca))" + client_cert: "((diego_rep_client.certificate))" + client_key: "((diego_rep_client.private_key))" + - name: metron_agent + release: loggregator + properties: *metron_agent_properties +- name: diego-cell + azs: + - z1 + - z2 + instances: 2 + vm_type: r3.xlarge + vm_extensions: + - 100GB_ephemeral_disk + stemcell: default + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + - name: cflinuxfs2-rootfs-setup + release: cflinuxfs2 + - name: garden + release: garden-runc + properties: + garden: + cleanup_process_dirs_on_wait: true + debug_listen_address: 127.0.0.1:17019 + default_container_grace_time: 0 + destroy_containers_on_start: true + graph_cleanup_threshold_in_mb: 0 + deny_networks: + - 0.0.0.0/0 + persistent_image_list: + - "/var/vcap/packages/cflinuxfs2/rootfs" + network_plugin: /var/vcap/packages/runc-cni/bin/garden-external-networker + network_plugin_extra_args: + - --configFile=/var/vcap/jobs/garden-cni/config/adapter.json + - name: rep + release: diego + properties: + diego: + rep: + bbs: *diego_bbs_client_properties + preloaded_rootfses: + - cflinuxfs2:/var/vcap/packages/cflinuxfs2/rootfs + require_tls: true + ca_cert: "((diego_rep_agent.ca))" + server_cert: "((diego_rep_agent.certificate))" + server_key: "((diego_rep_agent.private_key))" + enable_legacy_api_endpoints: false + tls: + ca_cert: "((diego_rep_agent.ca))" + cert: "((diego_rep_agent.certificate))" + key: "((diego_rep_agent.private_key))" + - name: metron_agent + release: loggregator + properties: *metron_agent_properties + - name: cfdot + release: diego + properties: + diego: + cfdot: + bbs: *diego_bbs_client_properties + - name: route_emitter + release: diego + properties: + diego: + route_emitter: + local_mode: true + bbs: + ca_cert: "((diego_bbs_client.ca))" + client_cert: "((diego_bbs_client.certificate))" + client_key: "((diego_bbs_client.private_key))" + tcp: + enabled: true + uaa: + ca_cert: "((uaa_ca.certificate))" + client_secret: "((uaa_clients_tcp_emitter_secret))" + - name: garden-cni + release: cf-networking + - name: netmon + release: cf-networking + - name: vxlan-policy-agent + release: cf-networking + properties: + cf_networking: + vxlan_policy_agent: + ca_cert: ((network_policy_client.ca)) + client_cert: ((network_policy_client.certificate)) + client_key: ((network_policy_client.private_key)) + - name: silk-daemon + release: cf-networking + properties: + cf_networking: + silk_daemon: + ca_cert: ((silk_daemon.ca)) + client_cert: ((silk_daemon.certificate)) + client_key: ((silk_daemon.private_key)) + - name: silk-cni + release: cf-networking +- name: cc-clock + azs: + - z1 + instances: 1 + vm_type: m3.large + vm_extensions: + - 10GB_ephemeral_disk + stemcell: default + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + - name: cloud_controller_clock + release: capi + properties: + hm9000: + port: -1 + cc: + db_encryption_key: "((cc_db_encryption_key))" + default_to_diego_backend: true + install_buildpacks: *cc_install_buildpacks + default_running_security_groups: *cc_default_running_security_groups + default_staging_security_groups: *cc_default_staging_security_groups + security_group_definitions: *cc_security_group_definitions + internal_api_password: "((cc_internal_api_password))" + bulk_api_password: "((cc_bulk_api_password))" + quota_definitions: *quota_definitions + staging_upload_user: staging_user + staging_upload_password: "((cc_staging_upload_password))" + resource_pool: *blobstore-properties + packages: *blobstore-properties + droplets: *blobstore-properties + buildpacks: *blobstore-properties + mutual_tls: *cc_mutual_tls + ccdb: *ccdb + system_domain: "((system_domain))" + app_domains: *app_domains + routing_api: *routing_api + uaa: + ca_cert: "((uaa_ssl.ca))" + clients: + cc-service-dashboards: + secret: "((uaa_clients_cc-service-dashboards_secret))" + cc_routing: + secret: "((uaa_clients_cc-routing_secret))" + url: https://uaa.((system_domain)) + ssl: + port: 8443 + - name: metron_agent + release: loggregator + properties: *metron_agent_properties + - name: statsd_injector + release: statsd-injector + properties: *statsd_injector_properties +- name: cc-bridge + azs: + - z1 + - z2 + instances: 2 + vm_type: m3.medium + vm_extensions: + - 10GB_ephemeral_disk + stemcell: default + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + - name: stager + release: capi + properties: + capi: + stager: + bbs: *diego_bbs_client_properties + cc: + basic_auth_password: "((cc_internal_api_password))" + - name: nsync + release: capi + properties: + diego: + ssl: *ssl + capi: + nsync: + bbs: *diego_bbs_client_properties + cc: + basic_auth_password: "((cc_internal_api_password))" + base_url: https://api.((system_domain)) + - name: tps + release: capi + properties: + capi: + tps: + bbs: *diego_bbs_client_properties + cc: + ca_cert: "((cc_bridge_tps.ca))" + client_cert: "((cc_bridge_tps.certificate))" + client_key: "((cc_bridge_tps.private_key))" + - name: cc_uploader + release: capi + properties: + capi: + cc_uploader: + cc: + ca_cert: "((cc_bridge_cc_uploader.ca))" + client_cert: "((cc_bridge_cc_uploader.certificate))" + client_key: "((cc_bridge_cc_uploader.private_key))" + mutual_tls: + ca_cert: "((cc_bridge_cc_uploader_server.ca))" + server_cert: "((cc_bridge_cc_uploader_server.certificate))" + server_key: "((cc_bridge_cc_uploader_server.private_key))" + - name: metron_agent + release: loggregator + properties: *metron_agent_properties +- name: log-api + azs: + - z1 + - z2 + instances: 2 + vm_type: t2.small + vm_extensions: + - 10GB_ephemeral_disk + stemcell: default + update: + max_in_flight: 1 + serial: true + networks: + - name: default + jobs: + - name: consul_agent + release: consul + consumes: + consul_common: {from: consul_common_link} + consul_server: nil + consul_client: {from: consul_client_link} + properties: + consul: + agent: + services: + loggregator_trafficcontroller: {} + - name: loggregator_trafficcontroller + release: loggregator + properties: + traffic_controller: + etcd: + client_cert: "((etcd_client.certificate))" + client_key: "((etcd_client.private_key))" + uaa: + url: https://uaa.((system_domain)) + loggregator: + tls: + ca_cert: "((loggregator_tls_tc.ca))" + trafficcontroller: + cert: "((loggregator_tls_tc.certificate))" + key: "((loggregator_tls_tc.private_key))" + etcd: + require_ssl: true + ca_cert: "((etcd_server.ca))" + machines: + - cf-etcd.service.cf.internal + uaa: + client_secret: "((uaa_clients_doppler_secret))" + system_domain: "((system_domain))" + ssl: *ssl + cc: + srv_api_uri: "http://cloud-controller-ng.service.cf.internal:9022" + - name: route_registrar + release: routing + properties: + route_registrar: + routes: + - name: loggregator + port: 8080 + registration_interval: 20s + uris: + - loggregator.((system_domain)) + - name: doppler + port: 8081 + registration_interval: 20s + uris: + - doppler.((system_domain)) + - "*.doppler.((system_domain))" + - name: metron_agent + release: loggregator + properties: *metron_agent_properties + +variables: +- name: blobstore_admin_users_password + type: password +- name: blobstore_secure_link_secret + type: password +- name: cc_bulk_api_password + type: password +- name: cc_db_encryption_key + type: password +- name: cc_internal_api_password + type: password +- name: cc_staging_upload_password + type: password +- name: cf_mysql_mysql_admin_password + type: password +- name: cf_mysql_mysql_cluster_health_password + type: password +- name: cf_mysql_mysql_galera_healthcheck_endpoint_password + type: password +- name: cf_mysql_mysql_galera_healthcheck_password + type: password +- name: cf_mysql_proxy_api_password + type: password +- name: cc_database_password + type: password +- name: diego_database_password + type: password +- name: uaa_database_password + type: password +- name: routing_api_database_password + type: password +- name: network_policy_database_password + type: password +- name: network_connectivity_database_password + type: password +- name: uaa_clients_network_policy_secret + type: password +- name: silk_ca + type: certificate + options: + is_ca: true + common_name: silk-ca +- name: silk_controller + type: certificate + options: + ca: silk_ca + common_name: silk-controller.service.cf.internal + extended_key_usage: + - server_auth +- name: silk_daemon + type: certificate + options: + ca: silk_ca + common_name: silk-daemon + extended_key_usage: + - client_auth +- name: network_policy_ca + type: certificate + options: + is_ca: true + common_name: networkPolicyCA +- name: network_policy_server + type: certificate + options: + ca: network_policy_ca + common_name: policy-server.service.cf.internal + extended_key_usage: + - server_auth +- name: network_policy_client + type: certificate + options: + ca: network_policy_ca + common_name: clientName + extended_key_usage: + - client_auth +- name: uaa_clients_routing_api_client_secret + type: password +- name: uaa_clients_tcp_emitter_secret + type: password +- name: nats_password + type: password +- name: router_status_password + type: password +- name: cf_admin_password + type: password +- name: dropsonde_shared_secret + type: password +- name: router_route_services_secret + type: password +- name: uaa_admin_client_secret + type: password +- name: uaa_clients_cc-routing_secret + type: password +- name: uaa_clients_cc-service-dashboards_secret + type: password +- name: uaa_clients_cloud_controller_username_lookup_secret + type: password +- name: uaa_clients_doppler_secret + type: password +- name: uaa_clients_gorouter_secret + type: password +- name: uaa_clients_ssh-proxy_secret + type: password +- name: diego_bbs_encryption_keys_passphrase + type: password +- name: consul_encrypt_key + type: password +- name: diego_ssh_proxy_host_key + type: ssh +- name: uaa_jwt_signing_key + type: rsa +- name: service_cf_internal_ca + type: certificate + options: + is_ca: true + common_name: internalCA +- name: etcd_ca + type: certificate + options: + is_ca: true + common_name: etcdCA +- name: etcd_server + type: certificate + options: + ca: etcd_ca + common_name: etcd.service.cf.internal + alternative_names: + - "*.etcd.service.cf.internal" + - etcd.service.cf.internal + - "*.cf-etcd.service.cf.internal" + - cf-etcd.service.cf.internal + extended_key_usage: + - server_auth +- name: etcd_client + type: certificate + options: + ca: etcd_ca + common_name: clientName + extended_key_usage: + - client_auth +- name: etcd_peer_ca + type: certificate + options: + is_ca: true + common_name: peerCA +- name: etcd_peer + type: certificate + options: + ca: etcd_peer_ca + common_name: etcd.service.cf.internal + alternative_names: + - "*.etcd.service.cf.internal" + - etcd.service.cf.internal + - "*.cf-etcd.service.cf.internal" + - cf-etcd.service.cf.internal + extended_key_usage: + - client_auth + - server_auth +- name: blobstore_tls + type: certificate + options: + ca: service_cf_internal_ca + common_name: blobstore.service.cf.internal +- name: consul_agent_ca + type: certificate + options: + is_ca: true + common_name: consulCA +- name: consul_agent + type: certificate + options: + ca: consul_agent_ca + common_name: consul_agent + extended_key_usage: + - client_auth + - server_auth + alternative_names: + - 127.0.0.1 +- name: consul_server + type: certificate + options: + ca: consul_agent_ca + common_name: server.dc1.cf.internal + extended_key_usage: + - client_auth + - server_auth +- name: diego_auctioneer_client + type: certificate + options: + ca: service_cf_internal_ca + common_name: auctioneer client + extended_key_usage: + - client_auth +- name: diego_auctioneer_server + type: certificate + options: + ca: service_cf_internal_ca + common_name: auctioneer.service.cf.internal + extended_key_usage: + - server_auth + alternative_names: + - "*.auctioneer.service.cf.internal" + - auctioneer.service.cf.internal +- name: diego_bbs_client + type: certificate + options: + ca: service_cf_internal_ca + common_name: bbs client + extended_key_usage: + - client_auth +- name: diego_bbs_server + type: certificate + options: + ca: service_cf_internal_ca + common_name: bbs.service.cf.internal + extended_key_usage: + - server_auth + - client_auth + alternative_names: + - "*.bbs.service.cf.internal" + - bbs.service.cf.internal +- name: diego_rep_client + type: certificate + options: + ca: service_cf_internal_ca + common_name: rep client + extended_key_usage: + - client_auth +- name: diego_rep_agent + type: certificate + options: + ca: service_cf_internal_ca + common_name: cell.service.cf.internal + extended_key_usage: + - client_auth + - server_auth + alternative_names: + - "*.cell.service.cf.internal" + - cell.service.cf.internal +- name: loggregator_ca + type: certificate + options: + is_ca: true + common_name: loggregatorCA +- name: loggregator_tls_statsdinjector + type: certificate + options: + ca: loggregator_ca + common_name: statsdinjector + extended_key_usage: + - client_auth +- name: loggregator_tls_metron + type: certificate + options: + ca: loggregator_ca + common_name: metron + extended_key_usage: + - client_auth + - server_auth +- name: loggregator_tls_doppler + type: certificate + options: + ca: loggregator_ca + common_name: doppler + extended_key_usage: + - client_auth + - server_auth +- name: loggregator_tls_tc + type: certificate + options: + ca: loggregator_ca + common_name: trafficcontroller + extended_key_usage: + - client_auth + - server_auth +- name: loggregator_tls_syslogdrainbinder + type: certificate + options: + ca: service_cf_internal_ca + common_name: syslogdrainbinder + extended_key_usage: + - client_auth +- name: router_ca + type: certificate + options: + is_ca: true + common_name: routerCA +- name: router_ssl + type: certificate + options: + ca: router_ca + common_name: routerSSL + alternative_names: + - "((system_domain))" + - "*.((system_domain))" +- name: uaa_ca + type: certificate + options: + is_ca: true + common_name: uaaCA +- name: uaa_ssl + type: certificate + options: + ca: uaa_ca + common_name: uaa.service.cf.internal + alternative_names: + - uaa.service.cf.internal +- name: uaa_login_saml + type: certificate + options: + ca: uaa_ca + common_name: uaa_login_saml +- name: cc_tls + type: certificate + options: + ca: service_cf_internal_ca + common_name: cloud-controller-ng.service.cf.internal + extended_key_usage: + - client_auth + - server_auth +- name: cc_bridge_tps + type: certificate + options: + ca: service_cf_internal_ca + common_name: tps_watcher + extended_key_usage: + - client_auth +- name: cc_bridge_cc_uploader + type: certificate + options: + ca: service_cf_internal_ca + common_name: cc_uploader + extended_key_usage: + - client_auth +- name: cc_bridge_cc_uploader_server + type: certificate + options: + ca: service_cf_internal_ca + common_name: cc-uploader.service.cf.internal + extended_key_usage: + - server_auth + +releases: +- name: binary-buildpack + url: https://bosh.io/d/github.com/cloudfoundry/binary-buildpack-release?v=1.0.13 + version: 1.0.13 + sha1: f894e46d279cd965885f255439ea14188c48306d +- name: capi + url: https://bosh.io/d/github.com/cloudfoundry/capi-release?v=1.32.0 + version: 1.32.0 + sha1: 46d55d5f3a6858a8dbccfef32de5b95b6fc25294 +- name: cf-mysql + url: https://bosh.io/d/github.com/cloudfoundry/cf-mysql-release?v=36 + version: "36" + sha1: 746240c005465ffa105ee89e53e401e311b4fbf8 +- name: cf-networking + url: https://bosh.io/d/github.com/cloudfoundry-incubator/cf-networking-release?v=1.2.0 + version: 1.2.0 + sha1: c5a61b8be54e62b6d0eacfe2e0a45880345a3a21 +- name: cf-smoke-tests + url: https://bosh.io/d/github.com/cloudfoundry/cf-smoke-tests-release?v=36 + version: "36" + sha1: d943166254576036735a43a5ea57728276f83510 +- name: cflinuxfs2 + url: https://bosh.io/d/github.com/cloudfoundry/cflinuxfs2-release?v=1.137.0 + version: 1.137.0 + sha1: d89d3d31987c463768972678d7de8f94c439f6dc +- name: consul + url: https://bosh.io/d/github.com/cloudfoundry-incubator/consul-release?v=170 + version: "170" + sha1: 9a3d8138e785088f586bcedcc32fb82abdab120b +- name: diego + url: https://bosh.io/d/github.com/cloudfoundry/diego-release?v=1.22.0 + version: 1.22.0 + sha1: 5cb13b43347cb8e8a90a048aa72bf9e6f999398d +- name: dotnet-core-buildpack + url: https://bosh.io/d/github.com/cloudfoundry/dotnet-core-buildpack-release?v=1.0.20 + version: 1.0.20 + sha1: 2df8c20d16642f44fa78efce92d6d096bb80f390 +- name: etcd + url: https://bosh.io/d/github.com/cloudfoundry-incubator/etcd-release?v=112 + version: "112" + sha1: c912b285e6f0ea2d3f84b73ab582f02778499a24 +- name: garden-runc + url: https://bosh.io/d/github.com/cloudfoundry/garden-runc-release?v=1.9.0 + version: 1.9.0 + sha1: 77bfe8bdb2c3daec5b40f5116a6216badabd196c +- name: go-buildpack + url: https://bosh.io/d/github.com/cloudfoundry/go-buildpack-release?v=1.8.5 + version: 1.8.5 + sha1: e38c66d8bc1e9af917e083724f1bb1f9f0f3210f +- name: java-buildpack + url: https://bosh.io/d/github.com/cloudfoundry/java-buildpack-release?v=3.18 + version: "3.18" + sha1: 8311ef3ce3936e30d3c90259660ea856c0aaffaf +- name: loggregator + url: https://bosh.io/d/github.com/cloudfoundry/loggregator?v=91 + version: "91" + sha1: 5acc47a70f5fa24eda85e72e72390eac968882f0 +- name: nats + url: https://bosh.io/d/github.com/cloudfoundry/nats-release?v=21 + version: "21" + sha1: 2ac57ef37af7bc96243d06b4becaa32f2ce109fd +- name: nodejs-buildpack + url: https://bosh.io/d/github.com/cloudfoundry/nodejs-buildpack-release?v=1.5.36 + version: 1.5.36 + sha1: 759ec20ed42e4795dc0a49022e6a605cb9af2bec +- name: php-buildpack + url: https://bosh.io/d/github.com/cloudfoundry/php-buildpack-release?v=4.3.35 + version: 4.3.35 + sha1: 1991c9990c9548a5bc494214fb93c1a3f06d75b3 +- name: python-buildpack + url: https://bosh.io/d/github.com/cloudfoundry/python-buildpack-release?v=1.5.19 + version: 1.5.19 + sha1: 4ab0f8d0fc0f93adbb2cfde53f1c93dc3c41a283 +- name: routing + url: https://bosh.io/d/github.com/cloudfoundry-incubator/cf-routing-release?v=0.159.0 + version: 0.159.0 + sha1: 739f663f976fea826595880cfd58470bc8d49172 +- name: ruby-buildpack + url: https://bosh.io/d/github.com/cloudfoundry/ruby-buildpack-release?v=1.6.41 + version: 1.6.41 + sha1: 6ed806abf2b3754506aecdcb72de5cdaaedfa4a7 +- name: staticfile-buildpack + url: https://bosh.io/d/github.com/cloudfoundry/staticfile-buildpack-release?v=1.4.9 + version: 1.4.9 + sha1: b05357c7de4adb910a14f07a5eec6acdcc525e45 +- name: statsd-injector + url: https://bosh.io/d/github.com/cloudfoundry/statsd-injector-release?v=1.0.28 + version: 1.0.28 + sha1: 655c86c44731fb88c10dc3628d77dfb8ebd9da26 +- name: uaa + url: https://bosh.io/d/github.com/cloudfoundry/uaa-release?v=41 + version: "41" + sha1: a1b1818fb4f5e722a6c3073d364ce4c400e5fb31 +stemcells: +- alias: default + os: ubuntu-trusty + version: "3421.11" diff --git a/headless-services/bosh-language-server/src/test/resources/workspace/pipeline-with-bad-resources.yml b/headless-services/bosh-language-server/src/test/resources/workspace/pipeline-with-bad-resources.yml deleted file mode 100644 index 44c8d1607..000000000 --- a/headless-services/bosh-language-server/src/test/resources/workspace/pipeline-with-bad-resources.yml +++ /dev/null @@ -1,23 +0,0 @@ -resources: -- name: sts4 - type: git - source: - repository: https://github.com/kdvolder/somestuff -- name: other-repo - type: git - source: - repository: https://github.com/kdvolder/somestuff -- name: more-stuff - type: git - source: - repository: https://github.com/kdvolder/somestuff -jobs: -- name: job1 - plan: - - get: sts4 - - task: do-stuff - input_mapping: - task_input: bogus_input - repo: sts4 - - get: bogus-get - - put: bogus-put diff --git a/headless-services/bosh-language-server/src/test/resources/workspace/pipeline.yml b/headless-services/bosh-language-server/src/test/resources/workspace/pipeline.yml deleted file mode 100644 index 6c3577111..000000000 --- a/headless-services/bosh-language-server/src/test/resources/workspace/pipeline.yml +++ /dev/null @@ -1,115 +0,0 @@ -########################################################## -resource_types: -- name: s3-multi - type: docker-image - source: - repository: kdvolder/s3-resource-simple -- name: slack-notification - type: docker-image - source: - repository: cfcommunity/slack-notification-resource - tag: latest -######################################################### -resources: -- name: docker-git - type: git - source: - uri: git@github.com:spring-projects/sts4.git - branch: {{branch}} - username: kdvolder - private_key: {{rsa_id}} - paths: - - concourse/docker -- name: sts4 - type: git - source: - uri: git@github.com:spring-projects/sts4.git - branch: {{branch}} - private_key: {{rsa_id}} -- name: s3-boot-properties-vsix - type: s3 - source: - bucket: {{s3_bucket}} - access_key_id: {{s3_accesskey}} - secret_access_key: {{s3_secretkey}} - region_name: {{s3_region}} - regexp: sts4/vscode-extensions/vscode-boot-properties-(.*).vsix -- name: s3-manifest-yaml-vsix - type: s3 - source: - bucket: {{s3_bucket}} - access_key_id: {{s3_accesskey}} - secret_access_key: {{s3_secretkey}} - region_name: {{s3_region}} - regexp: sts4/vscode-extensions/vscode-manifest-yaml-(.*).vsix -- name: website - type: s3-multi - source: - bucket: {{s3_prod_bucket}} - access_key_id: {{s3_prod_accesskey}} - secret_access_key: {{s3_prod_secretkey}} - region_name: {{s3_region}} - path: snapshot/STS4/vscode-extensions - options: - - "--acl public-read" -- name: slack-notification - type: slack-notification - source: - url: https://hooks.slack.com/services/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 -- name: docker-image - type: docker-image - source: - username: {{docker_hub_username}} - password: {{docker_hub_password}} - repository: kdvolder/sts4-build-env -######################################################################################## -jobs: -- name: build-docker-image - serial: true - plan: - - get: docker-git - trigger: true - - put: docker-image - params: - build: docker-git/concourse/docker - get_params: - skip_download: true -- name: build-vsix - plan: - - get: sts4 - trigger: true - - task: build-vscode-extensions - file: sts4/concourse/tasks/build-vscode-extensions.yml - on_success: - aggregate: - - put: s3-manifest-yaml-vsix - params: - file: vsix-files/vscode-manifest-yaml-*.vsix - acl: public-read - - put: s3-boot-properties-vsix - params: - file: vsix-files/vscode-boot-properties-*.vsix - acl: public-read - on_failure: - put: slack-notification - params: - channel: "@kdvolder" - text: | - Concourse ${BUILD_PIPELINE_NAME}/${BUILD_JOB_NAME}/${BUILD_NAME} has failed! -- name: build-website - plan: - - aggregate: - - get: sts4 - - get: s3-manifest-yaml-vsix - passed: - - build-vsix - trigger: true - - get: s3-boot-properties-vsix - passed: - - build-vsix - trigger: true - - task: build-website - file: sts4/concourse/tasks/build-website.yml - - put: website - params: - path: website diff --git a/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-stuff.yml b/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-stuff.yml deleted file mode 100644 index e69de29bb..000000000 diff --git a/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-vscode-extensions.sh b/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-vscode-extensions.sh deleted file mode 100755 index c18fdfb05..000000000 --- a/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-vscode-extensions.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -e -workdir=`pwd` - -cd sts4/vscode-extensions -./build-all.sh - -cd $workdir -cp `find sts4/vscode-extensions -name "*.vsix"` vsix-files - diff --git a/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-vscode-extensions.yml b/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-vscode-extensions.yml deleted file mode 100644 index 11a108490..000000000 --- a/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-vscode-extensions.yml +++ /dev/null @@ -1,11 +0,0 @@ -inputs: -- name: sts4 -outputs: -- name: vsix-files -platform: linux -image_resource: - type: docker-image - source: - repository: kdvolder/sts4-build-env -run: - path: "sts4/concourse/tasks/build-vscode-extensions.sh" diff --git a/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-website.sh b/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-website.sh deleted file mode 100755 index 50ec31994..000000000 --- a/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-website.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -set -e -workdir=`pwd` -sources=$workdir/sts4/eclipse-distribution/common/html -target=$workdir/website - -#cp -r "${sources}/stylesheet.css" "$target" -#cp -r ${sources}/*.js "$target" -#cp s3-manifest-yaml-vsix/*.vsix "$target" -#cp s3-boot-properties-vsix/*.vsix "$target" - -export vscode_manifest_yaml=$(basename s3-manifest-yaml-vsix/*.vsix) -echo "vscode_manifest_yaml=$vscode_manifest_yaml" -export vscode_boot_properties=$(basename s3-boot-properties-vsix/*.vsix) -echo "vscode_boot_properties=$vscode_boot_properties" - -envsubst > "$target/vscode-extensions-snippet.html" << XXXXXX -

-XXXXXX - -export vscode_snippet=`cat "$target/vscode-extensions-snippet.html"` - -envsubst > "$target/vscode-extensions.html" << XXXXXX - - - - -

STS4 Vscode Extensions

- -$vscode_snippet - - - -XXXXXX - -cat $target/vscode-extensions.html diff --git a/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-website.yml b/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-website.yml deleted file mode 100644 index c6325f3e7..000000000 --- a/headless-services/bosh-language-server/src/test/resources/workspace/tasks/build-website.yml +++ /dev/null @@ -1,13 +0,0 @@ -inputs: -- name: sts4 -- name: s3-manifest-yaml-vsix -- name: s3-boot-properties-vsix -outputs: -- name: website -platform: linux -image_resource: - type: docker-image - source: - repository: kdvolder/sts4-build-env -run: - path: "sts4/concourse/tasks/build-website.sh" diff --git a/headless-services/bosh-language-server/src/test/resources/workspace/tasks/test-stuff.yml b/headless-services/bosh-language-server/src/test/resources/workspace/tasks/test-stuff.yml deleted file mode 100644 index e69de29bb..000000000 diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index 34a8d3b84..5c81d9ef7 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -211,7 +211,7 @@ public class SpringBootApp { if (env != null) { JSONObject portsObject = env.getJSONObject("server.ports"); if (portsObject != null) { - String portValue = portsObject.getString("local.server.port"); + String portValue = portsObject.get("local.server.port").toString(); return portValue; } } diff --git a/headless-services/commons/commons-cf/pom.xml b/headless-services/commons/commons-cf/pom.xml index ddfa1ebbf..587de437c 100644 --- a/headless-services/commons/commons-cf/pom.xml +++ b/headless-services/commons/commons-cf/pom.xml @@ -32,7 +32,7 @@ org.mockito mockito-all - 1.10.19 + ${mockito-version} test diff --git a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java index d1d12b5f6..54018b13a 100644 --- a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java +++ b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/MavenBuilder.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeoutException; import org.springframework.ide.vscode.commons.util.ExternalCommand; import org.springframework.ide.vscode.commons.util.ExternalProcess; @@ -28,7 +29,7 @@ public class MavenBuilder { private List properties; - public void execute() throws IOException, InterruptedException { + public void execute() throws IOException, InterruptedException, TimeoutException { Path mvnwPath = System.getProperty("os.name").toLowerCase().startsWith("win") ? projectPath.resolve("mvnw.cmd") : projectPath.resolve("mvnw"); mvnwPath.toFile().setExecutable(true); diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalCommand.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalCommand.java index 39895018d..8999daccd 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalCommand.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalCommand.java @@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.util; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.concurrent.TimeoutException; /** * Encapsulates information about an 'external' command that can be run through the OS. @@ -68,7 +69,7 @@ public class ExternalCommand { * result of commands are logged to the console and if the command returns non * 0 exit value an exception is thrown. */ - public void exec(File workdir) throws IOException, InterruptedException { + public void exec(File workdir) throws IOException, InterruptedException, TimeoutException { System.out.println(">>> exec: "+this); ExternalProcess process = new ExternalProcess(workdir, this); System.out.println(process); diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalProcess.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalProcess.java index a5ee4d077..b1d5a0122 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalProcess.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/ExternalProcess.java @@ -16,6 +16,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** @@ -113,13 +116,14 @@ public class ExternalProcess { * Creates an external process and waits for it to terminate. The output and error streams * will be read and forwarded to System.out and System.err */ - public ExternalProcess(File workingDir, ExternalCommand cmd) throws IOException, InterruptedException { - this(workingDir, cmd, false); + public ExternalProcess(File workingDir, ExternalCommand cmd) throws IOException, InterruptedException, TimeoutException { + this(workingDir, cmd, false, null); } private void init(File workingDir, ExternalCommand cmd, - OutputStream outStream, OutputStream errStream) throws IOException, - InterruptedException { + OutputStream outStream, OutputStream errStream, + Duration timeout) throws IOException, + InterruptedException, TimeoutException { this.cmd = cmd; ProcessBuilder processBuilder = new ProcessBuilder(cmd.getProgramAndArgs()); processBuilder.directory(workingDir); @@ -127,15 +131,33 @@ public class ExternalProcess { process = processBuilder.start(); err = new StreamGobler(process.getErrorStream(), errStream); out = new StreamGobler(process.getInputStream(), outStream); - exitValue = process.waitFor(); - } - - public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams) throws IOException, InterruptedException { - if (captureStreams) { - init(workingDir, cmd, new ByteArrayOutputStream(), new ByteArrayOutputStream()); + if (timeout==null) { + exitValue = process.waitFor(); } else { - init(workingDir, cmd, System.out, System.err); + if (process.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS)) { + exitValue = process.exitValue(); + } else { + process.destroy(); + exitValue = 999; //Set some non-0 value as that is what some callers might use to determine 'failure' occurred. + throw new TimeoutException("Command timed out: "+this); + } } + if (exitValue!=0) { + throw new IOException("Command execution failed:\n"+this); + } + } + + public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams, Duration timeout) throws IOException, InterruptedException, TimeoutException { + if (captureStreams) { + init(workingDir, cmd, new ByteArrayOutputStream(), new ByteArrayOutputStream(), timeout); + } else { + init(workingDir, cmd, System.out, System.err, timeout); + } + + } + + public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams) throws IOException, InterruptedException, TimeoutException { + this(workingDir, cmd, captureStreams, null); } public String getOut() throws InterruptedException { @@ -156,13 +178,13 @@ public class ExternalProcess { result.append("exitValue = "+exitValue+"\n"); String strOut = getOut(); if (strOut!=null) { - result.append("\n------- System.out -------\n"); + result.append("------- System.out -------\n"); result.append(strOut); } String strErr = getErr(); if (strErr!=null) { - result.append("\n------- System.err -------\n"); - result.append(getOut()); + result.append("------- System.err -------\n"); + result.append(strErr); } result.append("<<<< ExternalProcess"); return result.toString(); diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/LanguageId.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/LanguageId.java index 575a2ff1e..980fae8e3 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/LanguageId.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/text/LanguageId.java @@ -21,10 +21,12 @@ public class LanguageId { public static final LanguageId PLAINTEXT = of("plaintext"); public static final LanguageId CONCOURSE_TASK = of("concourse-task-yaml"); public static final LanguageId CONCOURSE_PIPELINE = of("concourse-pipeline-yaml"); - public static final LanguageId BOSH_DEPLOYMENT = of("bosh-deployment-manifest"); public static final LanguageId CF_MANIFEST = of("manifest-yaml"); public static final LanguageId JAVA = of("java"); public static final LanguageId YAML = of("yaml"); + + public static final LanguageId BOSH_DEPLOYMENT = of("bosh-deployment-manifest"); + public static final LanguageId BOSH_CLOUD_CONFIG = of("bosh-cloud-config"); private final String id; diff --git a/headless-services/commons/commons-yaml/.settings/org.eclipse.jdt.ui.prefs b/headless-services/commons/commons-yaml/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..68f26e829 --- /dev/null +++ b/headless-services/commons/commons-yaml/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,59 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java index e6aa0d844..43d12b476 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/ast/NodeUtil.java @@ -116,6 +116,18 @@ public class NodeUtil { return null; } + public static NodeTuple getPropertyTuple(Node node, String propName) { + if (node instanceof MappingNode) { + for (NodeTuple entry : ((MappingNode)node).getValue()) { + String key = NodeUtil.asScalar(entry.getKeyNode()); + if (propName.equals(key)) { + return entry; + } + } + } + return null; + } + public static Node getProperty(Node node, String propName) { if (node instanceof MappingNode) { for (NodeTuple entry : ((MappingNode)node).getValue()) { 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/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..036c31ea2 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) { @@ -215,7 +216,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { } for (YValueHint value : values) { double score = FuzzyMatcher.matchScore(query, value.getValue()); - if (score!=0 && !value.equals(query)) { + if (score!=0 && value!=null && !query.equals(value.getValue())) { int queryStart = offset-query.length(); DocumentEdits edits = new DocumentEdits(doc.getDocument()); edits.delete(queryStart, offset); @@ -357,7 +358,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext { } return null; } - + protected Collection getDashedCompletions(YamlDocument doc, SNode current, int offset) { try { YamlAssistContext relaxed = relaxForDashes(); diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ASTTypeCache.java similarity index 72% rename from headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java rename to headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ASTTypeCache.java index 2768dc50f..f39f5e3b8 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ASTTypeCache.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/ASTTypeCache.java @@ -8,8 +8,9 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.concourse; +package org.springframework.ide.vscode.commons.yaml.reconcile; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; @@ -18,13 +19,21 @@ import java.util.Map.Entry; import java.util.Set; import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.StringUtil; +import org.springframework.ide.vscode.commons.util.text.IDocument; +import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; -import org.springframework.ide.vscode.commons.yaml.reconcile.ITypeCollector; +import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; import org.springframework.ide.vscode.commons.yaml.schema.YType; import org.yaml.snakeyaml.nodes.Node; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; +import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Multimap; /** @@ -127,5 +136,33 @@ public class ASTTypeCache implements ITypeCollector { return typeIndex.get(uri); } + /** + * Use this astTypeCache to extract all defined names for a given type of definition. + */ + public Collection getDefinedNames(DynamicSchemaContext dc, YType defType) { + IDocument doc = dc.getDocument(); + if (doc!=null) { + String uri = doc.getUri(); + if (uri!=null) { + NodeTypes typeMap = getNodeTypes(uri); + if (typeMap!=null) { + Collection nodes = typeMap.getNodes(defType); + if (nodes!=null) { + ImmutableSet.Builder builder = ImmutableSortedSet.naturalOrder(); + for (Node node : nodes) { + String name = NodeUtil.asScalar(node); + if (StringUtil.hasText(name)) { + builder.add(name); + } + } + return builder.build(); + } + } + return ImmutableList.of(); + } + } + return null; + } + } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java index 114328423..73bff9672 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java @@ -45,6 +45,7 @@ import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment; import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes; import org.springframework.ide.vscode.commons.yaml.schema.ASTDynamicSchemaContext; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; +import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware; import org.springframework.ide.vscode.commons.yaml.schema.YType; import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil; import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; @@ -129,11 +130,11 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { return allOf(ast, node); } - private void reconcile(YamlFileAST ast, YamlPath path, Node parent, Node node, YType type) { + private void reconcile(YamlFileAST ast, YamlPath path, Node parent, Node node, YType _type) { // IDocument doc = ast.getDocument(); - if (type!=null) { + if (_type!=null) { DynamicSchemaContext schemaContext = new ASTDynamicSchemaContext(ast, path, node); - type = typeUtil.inferMoreSpecificType(type, schemaContext); + YType type = typeUtil.inferMoreSpecificType(_type, schemaContext); if (typeCollector!=null) { typeCollector.accept(node, type); } @@ -190,19 +191,23 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler { break; case scalar: if (typeUtil.isAtomic(type)) { - ValueParser parser = typeUtil.getValueParser(type, schemaContext); - if (parser!=null) { - try { - String value = NodeUtil.asScalar(node); - if (value!=null) { - parser.parse(value); - } - } catch (Exception e) { - ProblemType problemType = getProblemType(e); - DocumentRegion region = getRegion(e, ast.getDocument(), node); - String msg = getMessage(e); - valueParseError(type, region, msg, problemType, getValueReplacement(e)); - } + SchemaContextAware parserProvider = typeUtil.getValueParser(type); + if (parserProvider!=null) { + delayedConstraints.add(() -> { + parserProvider.safeWithContext(schemaContext).ifPresent(parser -> { + try { + String value = NodeUtil.asScalar(node); + if (value!=null) { + parser.parse(value); + } + } catch (Exception e) { + ProblemType problemType = getProblemType(e); + DocumentRegion region = getRegion(e, ast.getDocument(), node); + String msg = getMessage(e); + valueParseError(type, region, msg, problemType, getValueReplacement(e)); + } + }); + }); } } else { expectTypeButFoundScalar(type, node); diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDocumentSymbolHandler.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/TypeBasedYamlSymbolHandler.java similarity index 86% rename from headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDocumentSymbolHandler.java rename to headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/TypeBasedYamlSymbolHandler.java index 773b03b0c..e4ab8e752 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDocumentSymbolHandler.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/TypeBasedYamlSymbolHandler.java @@ -8,7 +8,7 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.concourse; +package org.springframework.ide.vscode.commons.yaml.reconcile; import java.util.Collection; import java.util.List; @@ -35,19 +35,20 @@ import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableSet; /** - * Finds symbols in a concourse document. This relies on type information cached - * during reconcile and stored in the {@link ConcourseModel}. Therefore, - * this handler only works if invoked after a reconcile. + * Finds symbols in a yaml document based on type information cached + * during reconcile and stored in the {@link ASTTypeCache}. Because + * it relies on information computed during reconcile, this handler only + * works if it is invoked after a reconcile. * * @author Kris De Volder */ -public class ConcourseDocumentSymbolHandler implements DocumentSymbolHandler { +public class TypeBasedYamlSymbolHandler implements DocumentSymbolHandler { private ASTTypeCache astTypeCache; private Set definitionTypes; private SimpleTextDocumentService documents; - public ConcourseDocumentSymbolHandler(SimpleTextDocumentService documents, ASTTypeCache astTypeCache, Collection definitionTypes) { + public TypeBasedYamlSymbolHandler(SimpleTextDocumentService documents, ASTTypeCache astTypeCache, Collection definitionTypes) { Assert.isTrue(!definitionTypes.isEmpty()); // If there's no interesting types then you are better of using DocumentSymbolHandler.NO_SYMBOLS this.documents = documents; this.astTypeCache = astTypeCache; diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java index e97ab8fde..c46c7f2cb 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/YamlSchemaProblems.java @@ -114,7 +114,7 @@ public class YamlSchemaProblems { return new ReconcileProblemImpl(problemType, msg, start, end-start); } - public static ReconcileProblemImpl missingProperty(String msg, IDocument doc, Node parent, MappingNode map) { + public static ReconcileProblemImpl missingProperty(String msg, IDocument doc, Node parent, Node map) { DocumentRegion underline = NodeUtil.region(doc, map); if (parent instanceof MappingNode) { for (NodeTuple prop : ((MappingNode) parent).getValue()) { diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SchemaContextAware.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SchemaContextAware.java index ff225c61f..e04b26212 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SchemaContextAware.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/SchemaContextAware.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.schema; +import java.util.Optional; + /** * Interface that can be implemented by something producing another * component (of some type `T`) where the returned component needs to @@ -19,7 +21,18 @@ package org.springframework.ide.vscode.commons.yaml.schema; */ @FunctionalInterface public interface SchemaContextAware { - T withContext(DynamicSchemaContext dc); + T withContext(DynamicSchemaContext dc) throws Exception; + + /** + * Like `withContext' method, but swallows exceptions silently. + */ + default Optional safeWithContext(DynamicSchemaContext dc) { + try { + return Optional.ofNullable(withContext(dc)); + } catch (Exception e) { + return Optional.empty(); + } + } /** * Convert a plain value into a {@link SchemaContextAware} that ignores the context and simply returns the value. 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..0c4455fbb 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 @@ -24,6 +24,7 @@ import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.function.BiFunction; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReplacementQuickfix; @@ -52,10 +53,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; @@ -158,7 +157,7 @@ public class YTypeFactory { * YTypeFactory */ public final YTypeUtil TYPE_UTIL = new YTypeUtil() { - + @Override public boolean isSequencable(YType type) { return ((AbstractType)type).isSequenceable(); @@ -210,8 +209,8 @@ public class YTypeFactory { } @Override - public ValueParser getValueParser(YType type, DynamicSchemaContext dc) { - return ((AbstractType)type).getParser(dc); + public SchemaContextAware getValueParser(YType type) { + return ((AbstractType)type).getParser(); } @Override @@ -239,6 +238,11 @@ public class YTypeFactory { public boolean tieredOptionalPropertyProposals() { return enableTieredOptionalPropertyProposals; } + + @Override + public boolean suggestDeprecatedProperties() { + return suggestDeprecatedProperties; + } }; ///////////////////////////////////////////////////////////////////////////////////// @@ -253,6 +257,8 @@ public class YTypeFactory { private List hints = new ArrayList<>(); private Map cachedPropertyMap; private SchemaContextAware>> hintProvider; + //TODO: SchemaContextAware now allows throwing exceptions so should be able to simplify the above to SchemaContextAware> + private List constraints = new ArrayList<>(2); private ISubCompletionEngine customContentAssistant = null; @@ -263,7 +269,7 @@ public class YTypeFactory { public ISubCompletionEngine getCustomContentAssistant() { return customContentAssistant; } - + public AbstractType setCustomContentAssistant(ISubCompletionEngine customContentAssistant) { this.customContentAssistant = customContentAssistant; return this; @@ -291,6 +297,7 @@ public class YTypeFactory { } public AbstractType addHintProvider(SchemaContextAware>> hintProvider) { + //TODO: SchemaContextAware now allows throwing exceptions so should be able to simplify the above to SchemaContextAware> this.hintProvider = hintProvider; return this; } @@ -402,8 +409,8 @@ public class YTypeFactory { parseWith((DynamicSchemaContext dc) -> parser); return this; } - private ValueParser getParser(DynamicSchemaContext dc) { - return parser == null ? null : parser.withContext(dc); + private SchemaContextAware getParser() { + return parser; } public AbstractType require(Constraint dynamicConstraint) { @@ -444,10 +451,7 @@ public class YTypeFactory { @Override public YType inferMoreSpecificType(DynamicSchemaContext dc) { if (dc!=null) { - YType inferred = typeGuesser.withContext(dc); - if (inferred!=null) { - return inferred; - } + return typeGuesser.safeWithContext(dc).orElse(this); } return this; } @@ -736,7 +740,7 @@ public class YTypeFactory { } - public static class YTypedPropertyImpl implements YTypedProperty { + public static class YTypedPropertyImpl implements YTypedProperty, Cloneable { final private String name; final private YType type; @@ -793,7 +797,7 @@ public class YTypeFactory { this.isDeprecated = deprecationMessage!=null; this.deprecationMessage = deprecationMessage; } - + @Override public String getDeprecationMessage() { return this.deprecationMessage; @@ -804,7 +808,7 @@ public class YTypeFactory { return this.isDeprecated; } - + public YTypedPropertyImpl isPrimary(boolean primary) { this.isPrimary = primary; this.isRequired = primary; @@ -816,12 +820,19 @@ public class YTypeFactory { this.isRequired = required; return this; } - + @Override public boolean isPrimary() { return isPrimary; } + public YTypedPropertyImpl copy() { + try { + return (YTypedPropertyImpl) super.clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException(e); + } + } } public YAtomicType yatomic(String name) { @@ -832,6 +843,10 @@ public class YTypeFactory { return new YTypedPropertyImpl(name, type); } + public YTypedPropertyImpl yprop(YTypedProperty prop) { + return ((YTypedPropertyImpl)prop).copy(); + } + public YAtomicType yenumFromHints(String name, BiFunction, String> errorMessageFormatter, SchemaContextAware> values) { YAtomicType t = yatomic(name); t.addHintProvider((dc) -> () -> values.withContext(dc)); @@ -847,6 +862,17 @@ public class YTypeFactory { return t; } + public YAtomicType yenumFromDynamicValues(String name, SchemaContextAware> values) { + return yenumFromHints(name, + //Error message formatter: + (parseString, validValues) -> "'"+parseString+"' is an unknown '"+name+"'. Valid values are: "+validValues, + //Hints provider: + (dc) -> + hints(values.withContext(dc) + ) + ); + } + public EnumTypeBuilder yenumBuilder(String name, String... values) { return new EnumTypeBuilder(name, values); } @@ -916,4 +942,10 @@ 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..7688e9099 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 @@ -33,7 +33,7 @@ public interface YTypeUtil { YValueHint[] getHintValues(YType yType, DynamicSchemaContext dc) throws Exception; String niceTypeName(YType type); YType getKeyType(YType type); - ValueParser getValueParser(YType type, DynamicSchemaContext dc); + SchemaContextAware getValueParser(YType type); //TODO: only one of these two should be enough? List getProperties(YType type); @@ -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(); } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YamlSchema.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YamlSchema.java index 11a0d42e7..0e11d16e1 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YamlSchema.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YamlSchema.java @@ -10,6 +10,8 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.yaml.schema; +import java.util.Collection; + import org.springframework.ide.vscode.commons.util.IntegerRange; /** diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java index 37b6b4674..a2d7d90c1 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraints.java @@ -11,18 +11,25 @@ package org.springframework.ide.vscode.commons.yaml.schema.constraints; import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems.EXTRA_PROPERTY; -import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems.missingProperty; +import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems.*; import static org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems.problem; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.function.Function; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType; import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.text.IDocument; import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; +import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache; +import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache.NodeTypes; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware; @@ -31,7 +38,11 @@ import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeTuple; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.Multiset; /** * Various static methods for constructing/composing {@link Constraint}s. @@ -39,7 +50,7 @@ import com.google.common.collect.ImmutableSet; * @author Kris De Volder */ public class Constraints { - + public static Constraint requireOneOf(String... properties) { return new RequireOneOf(properties); } @@ -47,17 +58,28 @@ public class Constraints { public static Constraint requireAtMostOneOf(String... properties) { return new RequireOneOf(properties).allowFewer(true); } + + public static Constraint requireAtLeastOneOf(String... properties) { + return new RequireOneOf(properties).allowMultiple(true); + } + static private class RequireOneOf implements Constraint { private final String[] _requiredProps; private boolean allowFewer = false; + private boolean allowMultiple = false; public RequireOneOf(String[] properties) { Assert.isLegal(properties.length>1); this._requiredProps = properties; } + public Constraint allowMultiple(boolean b) { + this.allowMultiple = b; + return this; + } + public Constraint allowFewer(boolean b) { this.allowFewer = b; return this; @@ -78,7 +100,7 @@ public class Constraints { problems.accept(missingProperty( "One of "+requiredProps+" is required for '"+type+"'", doc, parent, map)); } - } else if (foundPropsCount>1) { + } else if (foundPropsCount>1 && !allowMultiple) { //Mark each of the found keys as a violation: for (NodeTuple entry : map.getValue()) { String key = NodeUtil.asScalar(entry.getKeyNode()); @@ -119,7 +141,57 @@ public class Constraints { @Deprecated public static Constraint schemaContextAware(SchemaContextAware dispatcher) { return (DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) -> { - dispatcher.withContext(dc).verify(dc, parent, node, type, problems); + dispatcher.safeWithContext(dc).ifPresent((constraint) -> constraint.verify(dc, parent, node, type, problems)); }; } + + public static Constraint mutuallyExclusive(String p1, String p2) { + return (DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) -> { + if (node instanceof MappingNode) { + MappingNode map = (MappingNode) node; + Set defined = dc.getDefinedProperties(); + if (defined.contains(p1) && defined.contains(p2)) { + for (NodeTuple tup : map.getValue()) { + Node keyNode = tup.getKeyNode(); + String key = NodeUtil.asScalar(keyNode); + if (p1.equals(key) || p1.equals(key)) { + problems.accept(problem(EXTRA_PROPERTY, + "Only one of '"+p1+"' and '"+p2+"' should be defined for '"+type+"'", keyNode + )); + } + } + } + } + }; + } + + /** + * Check that all nodes of a given type, across the AST represent unique names. + */ + public static Constraint uniqueDefinition(ASTTypeCache astTypes, YType defType, ProblemType problemType) { + return (DynamicSchemaContext dc, Node parent, Node _ignored_node, YType type, IProblemCollector problems) -> { + NodeTypes nodeTypes = astTypes.getNodeTypes(dc.getDocument().getUri()); + if (nodeTypes!=null) { + Collection nodes = nodeTypes.getNodes(defType); + if (nodes!=null && !nodes.isEmpty()) { + Multimap name2nodes = ArrayListMultimap.create(); + for (Node node : nodes) { + String name = NodeUtil.asScalar(node); + if (StringUtil.hasText(name)) { + name2nodes.put(name, node); + } + } + for (String name : name2nodes.keys()) { + Collection nodesForName = name2nodes.get(name); + if (nodesForName.size()>1) { + for (Node duplicateNode : nodesForName) { + problems.accept(YamlSchemaProblems.problem(problemType, "Duplicate '"+defType+"'", duplicateNode)); + } + } + } + } + } + }; + } + } \ No newline at end of file diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java index e97856ed1..423a26bc9 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java @@ -326,7 +326,7 @@ public class Editor { return completions; } - public void assertCompletionLabels(String... expectedLabels) throws Exception { + public List assertCompletionLabels(String... expectedLabels) throws Exception { StringBuilder expect = new StringBuilder(); StringBuilder actual = new StringBuilder(); for (String label : expectedLabels) { @@ -334,11 +334,13 @@ public class Editor { expect.append("\n"); } - for (CompletionItem completion : getCompletions()) { + List completions; + for (CompletionItem completion : completions = getCompletions()) { actual.append(completion.getLabel()); actual.append("\n"); } assertEquals(expect.toString(), actual.toString()); + return completions; } public void assertContainsCompletions(String... expectTextAfter) throws Exception { @@ -383,6 +385,32 @@ public class Editor { } } + public void assertContextualCompletions(LanguageId language, Predicate isInteresting, String textBefore, String... textAfter) throws Exception { + Editor editor = harness.newEditor(language, this.getText()); + editor.reconcile(); //this ensures the conText is parsed and its AST is cached (will be used for + //dynamic CA when the conText + textBefore is not parsable. + + textBefore = replaceSelection(textBefore); + textAfter = Arrays.stream(textAfter) + .map((String t) -> replaceSelection(t)) + .collect(Collectors.toList()).toArray(new String[0]); + editor.setText(textBefore); + editor.assertCompletions(isInteresting, textAfter); + } + + public void assertContextualCompletions(String textBefore, String... textAfter) throws Exception { + assertContextualCompletions(getLanguageId(), (x) -> true, textBefore, textAfter); + } + + private String replaceSelection(String replacement) { + try { + String text = getRawText(); + return text.substring(0, selectionStart) + replacement + text.substring(selectionEnd); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public void apply(CompletionItem completion) throws Exception { completion = harness.resolveCompletionItem(completion); TextEdit edit = completion.getTextEdit(); diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java index bcef9462a..05e5f6fc4 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java @@ -14,8 +14,11 @@ package org.springframework.ide.vscode.languageserver.testharness; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.ArrayList; @@ -80,6 +83,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.LanguageServer import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.ExceptionUtil; +import org.springframework.ide.vscode.commons.util.IOUtil; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -569,4 +573,16 @@ public class LanguageServerHarness { } }; } + + /** + * Create a new editor and populate contents from a file found on the (test) classpath. + */ + public Editor newEditorFromClasspath(String resourcePath) throws Exception { + try (InputStream is = LanguageServerHarness.class.getResourceAsStream(resourcePath)) { + if (is==null) { + fail("Couldn't find the resource: "+resourcePath); + } + return newEditor(IOUtil.toString(is)); + } + } } diff --git a/headless-services/commons/pom.xml b/headless-services/commons/pom.xml index 7c35b0a56..9e311511e 100644 --- a/headless-services/commons/pom.xml +++ b/headless-services/commons/pom.xml @@ -23,6 +23,7 @@ + spring-milestones Spring Milestones @@ -75,9 +77,10 @@ 3.5.2 1.7.22 19.0 + 1.10.19 2.5.0 2.10 - 0.2.0-SNAPSHOT + 0.2.1 3.0.5.RELEASE 0.6.0.RELEASE diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java index 77133bc36..1432c8506 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseDefinitionFinder.java @@ -23,6 +23,7 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil; import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; +import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache; import org.springframework.ide.vscode.commons.yaml.schema.YType; import org.yaml.snakeyaml.nodes.Node; diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java index d3f987773..6a5039c53 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseLanguageServer.java @@ -34,6 +34,7 @@ import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngi import org.springframework.ide.vscode.commons.yaml.completion.YamlCompletionEngineOptions; import org.springframework.ide.vscode.commons.yaml.hover.YamlHoverInfoProvider; import org.springframework.ide.vscode.commons.yaml.quickfix.YamlQuickfixes; +import org.springframework.ide.vscode.commons.yaml.reconcile.TypeBasedYamlSymbolHandler; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaBasedReconcileEngine; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; import org.springframework.ide.vscode.commons.yaml.schema.YType; @@ -74,7 +75,7 @@ public class ConcourseLanguageServer extends SimpleLanguageServer { reconcileEngine.setTypeCollector(models.getAstTypeCache()); this.symbolHandler = CollectionUtil.hasElements(definitionTypes) - ? new ConcourseDocumentSymbolHandler(documents, models.getAstTypeCache(), definitionTypes) + ? new TypeBasedYamlSymbolHandler(documents, models.getAstTypeCache(), definitionTypes) : DocumentSymbolHandler.NO_SYMBOLS; } diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java index 71ac7530b..9b83ff6a0 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java @@ -40,7 +40,9 @@ import org.springframework.ide.vscode.commons.yaml.path.NodeCursor; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment; import org.springframework.ide.vscode.commons.yaml.path.YamlTraversal; +import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache; import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems; +import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache.NodeTypes; import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint; import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext; import org.springframework.ide.vscode.commons.yaml.schema.YType; @@ -51,7 +53,6 @@ import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty; import org.springframework.ide.vscode.commons.yaml.schema.YValueHint; import org.springframework.ide.vscode.commons.yaml.util.Streams; -import org.springframework.ide.vscode.concourse.ASTTypeCache.NodeTypes; import org.springframework.ide.vscode.concourse.util.StaleFallbackCache; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.error.YAMLException; 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: <*>" ); } diff --git a/headless-services/manifest-yaml-language-server/pom.xml b/headless-services/manifest-yaml-language-server/pom.xml index 8f796ff9c..276cb6c62 100644 --- a/headless-services/manifest-yaml-language-server/pom.xml +++ b/headless-services/manifest-yaml-language-server/pom.xml @@ -57,7 +57,7 @@ org.mockito mockito-all - 1.10.19 + ${mockito-version} test