diff --git a/.editorconfig b/.editorconfig
index f29b4f297e..23c678bc5f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -15,15 +15,250 @@ trim_trailing_whitespace = true
[project.json]
indent_size = 2
-# C# files
-[*.cs]
+# Xml project files
+[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj,slnx}]
+indent_size = 2
+
+[*.{csproj,vbproj,proj,nativeproj,locproj}]
+charset = utf-8
+
+# Xml build files
+[*.builds]
+indent_size = 2
+
+# Xml files
+[*.{xml,stylecop,resx,ruleset}]
+indent_size = 2
+
+# Xml config files
+[*.{props,targets,config,nuspec}]
+indent_size = 2
+
+# YAML config files
+[*.{yml,yaml}]
+indent_size = 2
+
+# Shell scripts
+[*.sh]
+end_of_line = lf
+[*.{cmd, bat}]
+end_of_line = crlf
+
+# Markdown files
+[*.md]
+indent_size = 2
+
+# Keep in sync with https://github.com/dotnet/docker-tools/blob/main/.editorconfig
+# Do not copy suppressions for code-quality issues specific to docker-tools.
+# Samples and standalone scripts are outside this scope; test-app fixtures are excluded below.
+[{eng/update-dependencies,eng/shared,tests}/**.cs]
+tab_width = 4
+
+# CS1591: Missing XML comment for publicly visible type or member.
+# Public documentation comments are not required for repository tooling and tests.
+dotnet_diagnostic.CS1591.severity = none
+
+# Organize usings and license headers
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = true
+# IDE0005: Remove unnecessary using directives.
+dotnet_diagnostic.IDE0005.severity = warning
+file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.
+# IDE0073: Require file header.
+dotnet_diagnostic.IDE0073.severity = warning
+
+# this. preferences
+dotnet_style_qualification_for_event = false:warning
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_property = false:warning
+
+# Language keywords vs BCL types
+dotnet_style_predefined_type_for_locals_parameters_members = true:warning
+dotnet_style_predefined_type_for_member_access = true:warning
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
+
+# Expression-level preferences
+dotnet_prefer_system_hash_code = true:warning
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_collection_initializer = true:warning
+dotnet_style_explicit_tuple_names = true:warning
+dotnet_style_namespace_match_folder = true:warning
+dotnet_style_null_propagation = true:warning
+dotnet_style_object_initializer = true:warning
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_collection_expression = when_types_loosely_match:warning
+# IDE0305: Prefer collection expressions over fluent collection creation.
+# Keep fluent calls rather than converting them to collection spread expressions.
+dotnet_diagnostic.IDE0305.severity = silent
+dotnet_style_prefer_compound_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:warning
+dotnet_style_prefer_conditional_expression_over_return = false:silent
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:warning
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+dotnet_style_prefer_inferred_tuple_names = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
+dotnet_style_prefer_non_hidden_explicit_cast_in_source = true:warning
+dotnet_style_prefer_simplified_boolean_expressions = true:warning
+dotnet_style_prefer_simplified_interpolation = true:warning
+# IDE0001: Simplify qualified type names in declarations and expressions.
+dotnet_diagnostic.IDE0001.severity = warning
+# IDE0002: Simplify qualified static member access.
+dotnet_diagnostic.IDE0002.severity = warning
+# IDE0004: Remove unnecessary casts.
+dotnet_diagnostic.IDE0004.severity = warning
+# IDE0010: Add missing cases to switch statements.
+# Allow a default label to handle enum members without listing each one explicitly.
+dotnet_diagnostic.IDE0010.severity = silent
+# IDE0043: Detect invalid composite format strings.
+dotnet_diagnostic.IDE0043.severity = warning
+# IDE0051: Remove unused private members.
+dotnet_diagnostic.IDE0051.severity = warning
+# IDE0052: Remove private members whose values are written but never read.
+dotnet_diagnostic.IDE0052.severity = warning
+# IDE0082: Replace typeof(T).Name with nameof(T).
+dotnet_diagnostic.IDE0082.severity = warning
+# IDE0100: Remove unnecessary equality comparisons against Boolean constants.
+dotnet_diagnostic.IDE0100.severity = warning
+# IDE0120: Simplify LINQ expressions by removing redundant operations.
+dotnet_diagnostic.IDE0120.severity = warning
+# IDE0121: Replace LINQ Where type checks followed by Cast with OfType.
+dotnet_diagnostic.IDE0121.severity = warning
+# RE0001: Validate regular expression patterns.
+dotnet_diagnostic.RE0001.severity = warning
+# JSON001: Validate JSON strings.
+dotnet_diagnostic.JSON001.severity = warning
+
+# Field and parameter preferences
+dotnet_style_readonly_field = true:warning
+dotnet_code_quality_unused_parameters = all:warning
+# IDE0280: Use nameof instead of a string literal when an attribute names a symbol.
+dotnet_diagnostic.IDE0280.severity = warning
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+# IDE0076: Remove global SuppressMessage attributes with invalid scope or target values.
+dotnet_diagnostic.IDE0076.severity = warning
+# IDE0077: Use documentation comment IDs in global SuppressMessage attributes.
+dotnet_diagnostic.IDE0077.severity = warning
+# IDE0079: Remove suppressions that do not suppress a diagnostic.
+dotnet_diagnostic.IDE0079.severity = warning
+# IDE0370: Remove null-forgiving operators that do not suppress a nullable warning.
+dotnet_diagnostic.IDE0370.severity = warning
+
+# New line preferences
+dotnet_style_allow_multiple_blank_lines_experimental = false:warning
+dotnet_style_allow_statement_immediately_after_block_experimental = false:warning
+
+# var preferences
+csharp_style_var_elsewhere = false:warning
+csharp_style_var_for_built_in_types = false:warning
+csharp_style_var_when_type_is_apparent = false:warning
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = when_on_single_line:warning
+csharp_style_expression_bodied_constructors = when_on_single_line:warning
+csharp_style_expression_bodied_indexers = when_on_single_line:warning
+csharp_style_expression_bodied_lambdas = when_on_single_line:warning
+csharp_style_expression_bodied_local_functions = when_on_single_line:warning
+csharp_style_expression_bodied_methods = when_on_single_line:warning
+csharp_style_expression_bodied_operators = when_on_single_line:warning
+csharp_style_expression_bodied_properties = when_on_single_line:warning
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_prefer_extended_property_pattern = true:warning
+csharp_style_prefer_not_pattern = true:warning
+csharp_style_prefer_pattern_matching = true:warning
+csharp_style_prefer_switch_expression = true:warning
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:warning
+
+# Modifier preferences
+csharp_prefer_static_anonymous_function = true:warning
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:warning
+csharp_style_prefer_readonly_struct = true:warning
+csharp_style_prefer_readonly_struct_member = true:warning
+# IDE0064: Make readonly struct fields writable when the struct assigns to this.
+dotnet_diagnostic.IDE0064.severity = warning
+# IDE0380: Remove unnecessary unsafe modifiers.
+dotnet_diagnostic.IDE0380.severity = warning
+# IDE0390: Make async methods without await synchronous when their signatures can change.
+dotnet_diagnostic.IDE0390.severity = warning
+# IDE0391: Remove async from methods without await when their signatures cannot change.
+dotnet_diagnostic.IDE0391.severity = warning
+
+# Code-block preferences
+csharp_prefer_braces = true:warning
+csharp_prefer_simple_using_statement = true:warning
+csharp_prefer_system_threading_lock = true:warning
+csharp_style_namespace_declarations = file_scoped:warning
+csharp_style_prefer_labeled_jump_statements = true:warning
+csharp_style_prefer_method_group_conversion = true:warning
+csharp_style_prefer_primary_constructors = true:warning
+csharp_style_prefer_simple_property_accessors = true:warning
+csharp_style_prefer_top_level_statements = true:warning
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:warning
+csharp_style_deconstructed_variable_declaration = true:warning
+csharp_style_implicit_object_creation_when_type_is_apparent = false:warning
+csharp_style_inlined_variable_declaration = true:warning
+csharp_style_prefer_implicitly_typed_lambda_expression = true:warning
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_local_over_anonymous_function = true:warning
+csharp_style_prefer_null_check_over_type_check = true:warning
+csharp_style_prefer_range_operator = true:warning
+csharp_style_prefer_tuple_swap = true:warning
+csharp_style_prefer_unbound_generic_type_in_nameof = true:warning
+csharp_style_prefer_utf8_string_literals = true:warning
+csharp_style_throw_expression = true:warning
+csharp_style_unused_value_assignment_preference = discard_variable:warning
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+# IDE0072: Add missing arms to switch expressions.
+# Allow a discard arm to handle enum members without listing each one explicitly.
+dotnet_diagnostic.IDE0072.severity = silent
+# IDE0080: Remove unnecessary null-forgiving operators in is expressions.
+dotnet_diagnostic.IDE0080.severity = warning
+# IDE0110: Remove unnecessary discard designations in patterns.
+dotnet_diagnostic.IDE0110.severity = warning
+# IDE0240: Remove nullable directives that repeat the current nullable context.
+dotnet_diagnostic.IDE0240.severity = warning
+# IDE0241: Remove nullable directives that have no effect on nullable analysis.
+dotnet_diagnostic.IDE0241.severity = warning
+
+# using directive preferences
+csharp_using_directive_placement = outside_namespace:warning
+
+# New line preferences
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:warning
+csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false:warning
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning
+csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning
+
+# IDE0055: Enforce whitespace, indentation, and formatting rules on build.
+dotnet_diagnostic.IDE0055.severity = warning
+
# New line preferences
-csharp_new_line_before_open_brace = all
-csharp_new_line_before_else = true
csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
csharp_new_line_before_finally = true
-csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
@@ -31,103 +266,8 @@ csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
-csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current
-
-# Modifier preferences
-csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
-
-# avoid this. unless absolutely necessary
-dotnet_style_qualification_for_field = false:suggestion
-dotnet_style_qualification_for_property = false:suggestion
-dotnet_style_qualification_for_method = false:suggestion
-dotnet_style_qualification_for_event = false:suggestion
-
-# Types: use keywords instead of BCL types, and permit var only when the type is clear
-csharp_style_var_for_built_in_types = false:suggestion
-csharp_style_var_when_type_is_apparent = false:none
-csharp_style_var_elsewhere = false:suggestion
-dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
-dotnet_style_predefined_type_for_member_access = true:suggestion
-
-# name all constant fields using PascalCase
-dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
-dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
-dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
-dotnet_naming_symbols.constant_fields.applicable_kinds = field
-dotnet_naming_symbols.constant_fields.required_modifiers = const
-dotnet_naming_style.pascal_case_style.capitalization = pascal_case
-
-# static fields should have s_ prefix
-dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
-dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
-dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
-dotnet_naming_symbols.static_fields.applicable_kinds = field
-dotnet_naming_symbols.static_fields.required_modifiers = static
-dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
-dotnet_naming_style.static_prefix_style.required_prefix = s_
-dotnet_naming_style.static_prefix_style.capitalization = camel_case
-
-# internal and private fields should be _camelCase
-dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
-dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
-dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
-dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
-dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
-dotnet_naming_style.camel_case_underscore_style.required_prefix = _
-dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
-
-# Code style defaults
-csharp_using_directive_placement = outside_namespace:suggestion
-dotnet_sort_system_directives_first = true
-csharp_prefer_braces = true:silent
-csharp_preserve_single_line_blocks = true:none
-csharp_preserve_single_line_statements = false:none
-csharp_prefer_static_local_function = true:suggestion
-csharp_prefer_simple_using_statement = false:none
-csharp_style_prefer_switch_expression = true:suggestion
-
-# Code quality
-dotnet_style_readonly_field = true:suggestion
-dotnet_code_quality_unused_parameters = non_public:suggestion
-
-# Expression-level preferences
-dotnet_style_object_initializer = true:suggestion
-dotnet_style_collection_initializer = true:suggestion
-dotnet_style_explicit_tuple_names = true:suggestion
-dotnet_style_coalesce_expression = true:suggestion
-dotnet_style_null_propagation = true:suggestion
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
-dotnet_style_prefer_inferred_tuple_names = true:suggestion
-dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
-dotnet_style_prefer_auto_properties = true:suggestion
-dotnet_style_prefer_conditional_expression_over_assignment = true:silent
-dotnet_style_prefer_conditional_expression_over_return = true:silent
-csharp_prefer_simple_default_expression = true:suggestion
-
-# Expression-bodied members
-csharp_style_expression_bodied_methods = true:silent
-csharp_style_expression_bodied_constructors = true:silent
-csharp_style_expression_bodied_operators = true:silent
-csharp_style_expression_bodied_properties = true:silent
-csharp_style_expression_bodied_indexers = true:silent
-csharp_style_expression_bodied_accessors = true:silent
-csharp_style_expression_bodied_lambdas = true:silent
-csharp_style_expression_bodied_local_functions = true:silent
-
-# Pattern matching
-csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
-csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
-csharp_style_inlined_variable_declaration = true:suggestion
-
-# Null checking preferences
-csharp_style_throw_expression = true:suggestion
-csharp_style_conditional_delegate_call = true:suggestion
-
-# Other features
-csharp_style_prefer_index_operator = false:none
-csharp_style_prefer_range_operator = false:none
-csharp_style_pattern_local_over_anonymous_function = false:none
+csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
@@ -137,7 +277,7 @@ csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
-csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
@@ -153,42 +293,207 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
-# Analyzers
-dotnet_code_quality.ca1802.api_surface = private, internal
-dotnet_code_quality.ca2208.api_surface = public
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
-# License header
-file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.\n
+# Naming rules
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = warning
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
-# Xml project files
-[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj,slnx}]
-indent_size = 2
+dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = warning
+dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
+dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
-[*.{csproj,vbproj,proj,nativeproj,locproj}]
-charset = utf-8
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = warning
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
-# Xml build files
-[*.builds]
-indent_size = 2
+dotnet_naming_rule.methods_should_be_pascalcase.severity = warning
+dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
+dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
-# Xml files
-[*.{xml,stylecop,resx,ruleset}]
-indent_size = 2
+dotnet_naming_rule.properties_should_be_pascalcase.severity = warning
+dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
+dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
-# Xml config files
-[*.{props,targets,config,nuspec}]
-indent_size = 2
+dotnet_naming_rule.events_should_be_pascalcase.severity = warning
+dotnet_naming_rule.events_should_be_pascalcase.symbols = events
+dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
-# YAML config files
-[*.{yml,yaml}]
-indent_size = 2
+dotnet_naming_rule.local_variables_should_be_camelcase.severity = warning
+dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
+dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
-# Shell scripts
-[*.sh]
-end_of_line = lf
-[*.{cmd, bat}]
-end_of_line = crlf
+dotnet_naming_rule.local_constants_should_be_camelcase.severity = warning
+dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
+dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
-# Markdown files
-[*.md]
-indent_size = 2
+dotnet_naming_rule.parameters_should_be_camelcase.severity = warning
+dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
+dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.public_fields_should_be_pascalcase.severity = warning
+dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
+dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_fields_should_be__camelcase.severity = warning
+dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
+dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
+
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = warning
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
+
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = warning
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = warning
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = warning
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = warning
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.enums_should_be_pascalcase.severity = warning
+dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
+dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_functions_should_be_pascalcase.severity = warning
+dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
+dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = warning
+dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
+
+# Symbol specifications
+dotnet_naming_symbols.interfaces.applicable_kinds = interface
+dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interfaces.required_modifiers =
+
+dotnet_naming_symbols.enums.applicable_kinds = enum
+dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.enums.required_modifiers =
+
+dotnet_naming_symbols.events.applicable_kinds = event
+dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.events.required_modifiers =
+
+dotnet_naming_symbols.methods.applicable_kinds = method
+dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.methods.required_modifiers =
+
+dotnet_naming_symbols.properties.applicable_kinds = property
+dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.properties.required_modifiers =
+
+dotnet_naming_symbols.public_fields.applicable_kinds = field
+dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_fields.required_modifiers =
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_fields.required_modifiers =
+
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+
+dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
+dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types_and_namespaces.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+dotnet_naming_symbols.type_parameters.applicable_kinds = type_parameter
+dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
+dotnet_naming_symbols.type_parameters.required_modifiers =
+
+dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.local_variables.applicable_kinds = local
+dotnet_naming_symbols.local_variables.applicable_accessibilities = local
+dotnet_naming_symbols.local_variables.required_modifiers =
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.applicable_accessibilities = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+
+dotnet_naming_symbols.parameters.applicable_kinds = parameter
+dotnet_naming_symbols.parameters.applicable_accessibilities = *
+dotnet_naming_symbols.parameters.required_modifiers =
+
+dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.local_functions.applicable_kinds = local_function
+dotnet_naming_symbols.local_functions.applicable_accessibilities = *
+dotnet_naming_symbols.local_functions.required_modifiers =
+
+# Naming styles
+dotnet_naming_style.pascalcase.required_prefix =
+dotnet_naming_style.pascalcase.required_suffix =
+dotnet_naming_style.pascalcase.word_separator =
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+
+dotnet_naming_style.ipascalcase.required_prefix = I
+dotnet_naming_style.ipascalcase.required_suffix =
+dotnet_naming_style.ipascalcase.word_separator =
+dotnet_naming_style.ipascalcase.capitalization = pascal_case
+
+dotnet_naming_style.tpascalcase.required_prefix = T
+dotnet_naming_style.tpascalcase.required_suffix =
+dotnet_naming_style.tpascalcase.word_separator =
+dotnet_naming_style.tpascalcase.capitalization = pascal_case
+
+dotnet_naming_style._camelcase.required_prefix = _
+dotnet_naming_style._camelcase.required_suffix =
+dotnet_naming_style._camelcase.word_separator =
+dotnet_naming_style._camelcase.capitalization = camel_case
+
+dotnet_naming_style.camelcase.required_prefix =
+dotnet_naming_style.camelcase.required_suffix =
+dotnet_naming_style.camelcase.word_separator =
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_style.s_camelcase.required_prefix = s_
+dotnet_naming_style.s_camelcase.required_suffix =
+dotnet_naming_style.s_camelcase.word_separator =
+dotnet_naming_style.s_camelcase.capitalization = camel_case
+
+[eng/update-dependencies/**.cs]
+# CA1848: Prefer LoggerMessage delegates over ILogger extension methods.
+# CA1873: Guard potentially expensive logging arguments.
+# Source-generated logging is overkill for this project.
+dotnet_diagnostic.CA1848.severity = none
+dotnet_diagnostic.CA1873.severity = none
+
+[tests/**.cs]
+# CA1707: Remove underscores from identifier names.
+# Keep descriptive test names and version/repository identifiers.
+dotnet_diagnostic.CA1707.severity = none
+
+[tests/Microsoft.DotNet.Docker.Tests/TestAppArtifacts/**.cs]
+# These are inputs to generated test applications, not part of the test runner.
+generated_code = true
diff --git a/.github/workflows/update-dependencies-tests.yml b/.github/workflows/update-dependencies-tests.yml
index 5089a79d8a..d593747a65 100644
--- a/.github/workflows/update-dependencies-tests.yml
+++ b/.github/workflows/update-dependencies-tests.yml
@@ -3,8 +3,13 @@ name: Update Dependencies Tests
on:
pull_request:
paths:
+ - '.editorconfig'
+ - 'Directory.Build.props'
+ - 'Microsoft.DotNet.Docker.slnx'
- 'eng/update-dependencies/**'
- - 'tests/UpdateDependencies.Tests/**'
+ - 'eng/shared/**'
+ - 'tests/**'
+ - '!tests/Microsoft.DotNet.Docker.Tests/TestAppArtifacts/**'
- '.github/workflows/update-dependencies-tests.yml'
permissions: {}
@@ -28,13 +33,16 @@ jobs:
- name: Restore dependencies
run: >
dotnet restore
- tests/UpdateDependencies.Tests/UpdateDependencies.Tests.csproj
+ Microsoft.DotNet.Docker.slnx
--verbosity normal
- - name: Build tests
+ - name: Verify formatting
+ run: dotnet format Microsoft.DotNet.Docker.slnx --no-restore --verify-no-changes
+
+ - name: Build solution
run: >
dotnet build
- tests/UpdateDependencies.Tests/UpdateDependencies.Tests.csproj
+ Microsoft.DotNet.Docker.slnx
--no-restore
- name: Run tests
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000000..8ba22d031b
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,9 @@
+
+
+ Recommended
+ true
+ true
+
+ true
+
+
diff --git a/eng/shared/.editorconfig b/eng/shared/.editorconfig
deleted file mode 100644
index 46a15adc27..0000000000
--- a/eng/shared/.editorconfig
+++ /dev/null
@@ -1,5 +0,0 @@
-[*.cs]
-# CS1591: Missing XML comment for publicly visible type or member
-# https://learn.microsoft.com/dotnet/csharp/language-reference/compiler-messages/cs1591
-# Since this is a shared project, public members should be documented.
-dotnet_diagnostic.CS1591.severity = suggestion
diff --git a/eng/shared/DotNetVersion.cs b/eng/shared/DotNetVersion.cs
index 5115c6596b..f6d3502be2 100644
--- a/eng/shared/DotNetVersion.cs
+++ b/eng/shared/DotNetVersion.cs
@@ -1,11 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
+using System.Globalization;
using System.Text.RegularExpressions;
using NuGet.Versioning;
+#pragma warning disable CA1716 // Avoid language keywords in namespaces: this shared code is consumed only by C# projects.
namespace Microsoft.DotNet.Docker.Shared;
+#pragma warning restore CA1716
///
/// Represents the version of a .NET build artifact.
@@ -31,7 +33,7 @@ public sealed partial class DotNetVersion(SemanticVersion version) : SemanticVer
// SemanticVersion only handles full, strict semantic versions.
// If we can parse it as a full semantic version, use that. It resolves
// full build versions correctly including prerelease suffixes (e.g. '-preview.7')
- if (SemanticVersion.TryParse(versionString, out var fullSemanticVersion))
+ if (TryParse(versionString, out SemanticVersion? fullSemanticVersion))
{
return new DotNetVersion(fullSemanticVersion);
}
@@ -42,9 +44,9 @@ public sealed partial class DotNetVersion(SemanticVersion version) : SemanticVer
// we don't need to account for that in our parsing.
// Don't use System.Version.Parse here, because it defaults unmatched
// minor/patch versions to -1, whereas we want them to default to 0.
- var matchGroups = MajorMinorVersionRegex.Match(versionString).Groups;
- int major = int.Parse(matchGroups["major"].Value);
- int minor = int.TryParse(matchGroups["minor"].Value.TrimStart('.'), out int minorValue) ? minorValue : 0;
+ GroupCollection matchGroups = MajorMinorVersionRegex.Match(versionString).Groups;
+ int major = int.Parse(matchGroups["major"].Value, CultureInfo.InvariantCulture);
+ int minor = int.TryParse(matchGroups["minor"].Value.TrimStart('.'), NumberStyles.Integer, CultureInfo.InvariantCulture, out int minorValue) ? minorValue : 0;
return new DotNetVersion(new SemanticVersion(major, minor, 0));
}
diff --git a/eng/shared/Microsoft.DotNet.Docker.Shared.csproj b/eng/shared/Microsoft.DotNet.Docker.Shared.csproj
index a2d8181b00..a405a9b697 100644
--- a/eng/shared/Microsoft.DotNet.Docker.Shared.csproj
+++ b/eng/shared/Microsoft.DotNet.Docker.Shared.csproj
@@ -4,11 +4,7 @@
net10.0
enable
enable
- true
Microsoft.DotNet.Docker.Shared
-
-
- true
diff --git a/eng/update-dependencies/AzdoAuthProvider.cs b/eng/update-dependencies/AzdoAuthProvider.cs
index 2f4c56c8dd..9bc902e248 100644
--- a/eng/update-dependencies/AzdoAuthProvider.cs
+++ b/eng/update-dependencies/AzdoAuthProvider.cs
@@ -55,9 +55,9 @@ public string AccessToken
///
public VssConnection GetVssConnection(string azdoOrg)
{
- var baseUrl = new Uri(azdoOrg);
- var credential = new VssBasicCredential(userName: string.Empty, password: AccessToken);
- var connection = new VssConnection(baseUrl, credential);
+ Uri baseUrl = new Uri(azdoOrg);
+ VssBasicCredential credential = new VssBasicCredential(userName: string.Empty, password: AccessToken);
+ VssConnection connection = new VssConnection(baseUrl, credential);
return connection;
}
}
diff --git a/eng/update-dependencies/AzdoHttpClient.cs b/eng/update-dependencies/AzdoHttpClient.cs
index 458a956a01..cdd3bdecc8 100644
--- a/eng/update-dependencies/AzdoHttpClient.cs
+++ b/eng/update-dependencies/AzdoHttpClient.cs
@@ -11,7 +11,7 @@ internal class AzdoHttpClient(IAzdoAuthProvider azdoAuthProvider, HttpClient htt
{
public async Task GetAsync(string requestUri, CancellationToken ct = default)
{
- using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
+ using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri);
request.Headers.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(
diff --git a/eng/update-dependencies/BuildAssetService.cs b/eng/update-dependencies/BuildAssetService.cs
index f250913443..509010351a 100644
--- a/eng/update-dependencies/BuildAssetService.cs
+++ b/eng/update-dependencies/BuildAssetService.cs
@@ -22,14 +22,14 @@ public async Task GetAssetTextContentsAsync(Asset asset)
string url = ResolveAssetUrl(asset);
try
{
- _logger.LogInformation("Fetching contents from {url}", url);
- var response = await _httpClient.GetAsync(url);
+ _logger.LogInformation("Fetching contents from {Url}", url);
+ HttpResponseMessage response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
_logger.LogInformation(
"""
- Successfully fetched contents from {url}:
- {content}
+ Successfully fetched contents from {Url}:
+ {Content}
""",
url, content);
// Remove trailing newlines
@@ -37,22 +37,22 @@ public async Task GetAssetTextContentsAsync(Asset asset)
}
catch (Exception ex)
{
- _logger.LogError(ex, "Failed to fetch contents from {url}", url);
+ _logger.LogError(ex, "Failed to fetch contents from {Url}", url);
throw;
}
}
private string ResolveAssetUrl(Asset asset)
{
- var blobStorageLocations = asset.Locations.Where(l => l.Location.Contains("blob.core.windows.net"));
- var azdoLocations = asset.Locations.Where(l => l.Location.Contains("dev.azure.com"));
+ IEnumerable blobStorageLocations = asset.Locations.Where(l => l.Location.Contains("blob.core.windows.net"));
+ IEnumerable azdoLocations = asset.Locations.Where(l => l.Location.Contains("dev.azure.com"));
string allLocations =
string.Join(Environment.NewLine, asset.Locations.Select(l => $"Type: {l.Type}; Url: {l.Location}"));
_logger.LogInformation(
"""
- Asset {asset.Name} has {asset.Locations.Count} locations:
- {allLocations}
+ Asset {AssetName} has {LocationCount} locations:
+ {AllLocations}
""",
asset.Name, asset.Locations.Count, allLocations);
@@ -63,12 +63,9 @@ private string ResolveAssetUrl(Asset asset)
?? throw new InvalidOperationException(FormatErrorMessage(asset, "does not have any valid locations"));
string url = $"{bestLocation.Location}/{asset.Name}";
- _logger.LogInformation("Using location {url}", url);
+ _logger.LogInformation("Using location {Url}", url);
return url;
}
- private static string FormatErrorMessage(Asset asset, string message)
- {
- return $"Build {asset.BuildId} Asset {asset.Name} (version {asset.Version}) {message}";
- }
+ private static string FormatErrorMessage(Asset asset, string message) => $"Build {asset.BuildId} Asset {asset.Name} (version {asset.Version}) {message}";
}
diff --git a/eng/update-dependencies/BuildLabelService.cs b/eng/update-dependencies/BuildLabelService.cs
index 47f11bed62..b1e29a041a 100644
--- a/eng/update-dependencies/BuildLabelService.cs
+++ b/eng/update-dependencies/BuildLabelService.cs
@@ -29,7 +29,7 @@ internal sealed class BuildLabelService(TextWriter output) : IBuildLabelService
///
public void AddBuildTags(params IEnumerable tags)
{
- foreach (var tag in tags)
+ foreach (string tag in tags)
{
output.WriteLine($"##vso[build.addbuildtag]{tag}");
}
diff --git a/eng/update-dependencies/ChecksumHelper.cs b/eng/update-dependencies/ChecksumHelper.cs
index 51c9c1a5ea..83b1ce268e 100644
--- a/eng/update-dependencies/ChecksumHelper.cs
+++ b/eng/update-dependencies/ChecksumHelper.cs
@@ -1,4 +1,8 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
using System.Diagnostics;
+using System.Globalization;
using System.Security.Cryptography;
using System.Text;
@@ -17,18 +21,17 @@ internal static class ChecksumHelper
{
if (response.IsSuccessStatusCode)
{
- using (Stream httpStream = await response.Content.ReadAsStreamAsync(cancellationToken))
- using (SHA512 hash = SHA512.Create())
- {
- byte[] hashedInputBytes = hash.ComputeHash(httpStream);
+ using Stream httpStream = await response.Content.ReadAsStreamAsync(cancellationToken);
+ using SHA512 hash = SHA512.Create();
+ byte[] hashedInputBytes = hash.ComputeHash(httpStream);
- StringBuilder stringBuilder = new(128);
- foreach (byte b in hashedInputBytes)
- {
- stringBuilder.Append(b.ToString("X2"));
- }
- sha = stringBuilder.ToString();
+ StringBuilder stringBuilder = new(128);
+ foreach (byte b in hashedInputBytes)
+ {
+ stringBuilder.Append(b.ToString("X2", CultureInfo.InvariantCulture));
}
+
+ sha = stringBuilder.ToString();
}
else
{
diff --git a/eng/update-dependencies/Commands/CreatePullRequestOptions.cs b/eng/update-dependencies/Commands/CreatePullRequestOptions.cs
index 1f42c5cc25..bf4162d491 100644
--- a/eng/update-dependencies/Commands/CreatePullRequestOptions.cs
+++ b/eng/update-dependencies/Commands/CreatePullRequestOptions.cs
@@ -12,22 +12,22 @@ public record CreatePullRequestOptions
public string TargetBranch { get; set; } = "nightly";
public bool SubmitPullRequest { get; set; }
- private static readonly Option s_repoRoot = new("--repo-root")
+ private static readonly Option RepoRootOption = new("--repo-root")
{
Description = "The root of the dotnet-docker repo to run against (defaults to current working directory)",
DefaultValueFactory = _ => Directory.GetCurrentDirectory(),
};
- private static readonly Option s_submitPullRequest = new("--submit-pr")
+ private static readonly Option SubmitPullRequestOption = new("--submit-pr")
{
Description = "Apply updates in a temporary clone and submit a pull request with them."
+ " By default, updates are applied to the local repo and no git operations are run.",
};
- private static readonly Option s_sourceBranch = new("--source-branch")
+ private static readonly Option SourceBranchOption = new("--source-branch")
{
Description = "Branch to pull updates from",
DefaultValueFactory = _ => "",
};
- private static readonly Option s_targetBranch = new("--target-branch")
+ private static readonly Option TargetBranchOption = new("--target-branch")
{
Description = "Pull request will be submitted targeting this branch",
DefaultValueFactory = _ => "nightly",
@@ -35,10 +35,10 @@ public record CreatePullRequestOptions
public static void AddTo(Command command)
{
- command.Options.Add(s_repoRoot);
- command.Options.Add(s_submitPullRequest);
- command.Options.Add(s_sourceBranch);
- command.Options.Add(s_targetBranch);
+ command.Options.Add(RepoRootOption);
+ command.Options.Add(SubmitPullRequestOption);
+ command.Options.Add(SourceBranchOption);
+ command.Options.Add(TargetBranchOption);
}
public static CreatePullRequestOptions Bind(ParseResult result) =>
@@ -47,17 +47,17 @@ public static CreatePullRequestOptions Bind(ParseResult result) =>
protected static TOptions Bind(ParseResult result, TOptions options)
where TOptions : CreatePullRequestOptions
{
- options.RepoRoot = result.GetRequiredValue(s_repoRoot);
- options.SubmitPullRequest = result.GetValue(s_submitPullRequest);
- options.SourceBranch = result.GetRequiredValue(s_sourceBranch);
- options.TargetBranch = result.GetRequiredValue(s_targetBranch);
+ options.RepoRoot = result.GetRequiredValue(RepoRootOption);
+ options.SubmitPullRequest = result.GetValue(SubmitPullRequestOption);
+ options.SourceBranch = result.GetRequiredValue(SourceBranchOption);
+ options.TargetBranch = result.GetRequiredValue(TargetBranchOption);
return options;
}
public string CreatePullRequestBranchName(string name, string buildId = "")
{
- var buildIdSuffix = string.IsNullOrWhiteSpace(buildId) ? string.Empty : $"-{buildId}";
- var sanitizedTargetBranch = TargetBranch.Replace('/', '-');
+ string buildIdSuffix = string.IsNullOrWhiteSpace(buildId) ? string.Empty : $"-{buildId}";
+ string sanitizedTargetBranch = TargetBranch.Replace('/', '-');
return $"{sanitizedTargetBranch}/{name}{buildIdSuffix}";
}
}
diff --git a/eng/update-dependencies/Commands/DependencyCommand.cs b/eng/update-dependencies/Commands/DependencyCommand.cs
index 75370d6d5d..c9aacfcdf4 100644
--- a/eng/update-dependencies/Commands/DependencyCommand.cs
+++ b/eng/update-dependencies/Commands/DependencyCommand.cs
@@ -10,19 +10,27 @@ internal static class DependencyCommand
{
public static Command CreateCliCommand(IServiceProvider services) where TUpdater : class, IUpdater
{
- var command = new Command(TUpdater.Name, $"Update {TUpdater.Name}");
+ Command command = new Command(TUpdater.Name, $"Update {TUpdater.Name}");
if (typeof(IBarBuildUpdater).IsAssignableFrom(typeof(TUpdater)))
+ {
command.Subcommands.Add(FromBuildCommand.CreateCliCommand(services));
+ }
if (typeof(IBarChannelUpdater).IsAssignableFrom(typeof(TUpdater)))
+ {
command.Subcommands.Add(FromChannelCommand.CreateCliCommand(services));
+ }
if (typeof(IStagingPipelineUpdater).IsAssignableFrom(typeof(TUpdater)))
+ {
command.Subcommands.Add(FromStagingPipelineCommand.CreateCliCommand(services));
+ }
if (typeof(IGitHubReleaseUpdater).IsAssignableFrom(typeof(TUpdater)))
+ {
FromGitHubReleaseCommand.Configure(command, services);
+ }
return command;
}
diff --git a/eng/update-dependencies/Commands/FromBuildCommand.cs b/eng/update-dependencies/Commands/FromBuildCommand.cs
index 17cc4e3929..ed588f2830 100644
--- a/eng/update-dependencies/Commands/FromBuildCommand.cs
+++ b/eng/update-dependencies/Commands/FromBuildCommand.cs
@@ -13,15 +13,15 @@ internal static class FromBuildCommand
{
public static Command CreateCliCommand(IServiceProvider services) where TUpdater : class, IUpdater
{
- var command = new Command("build-id", "Update from a specific BAR build");
+ Command command = new Command("build-id", "Update from a specific BAR build");
FromBuildOptions.AddTo(command);
command.SetAction(async (result, cancellationToken) =>
{
FromBuildOptions options = FromBuildOptions.Bind(result);
- var barClient = services.GetRequiredService();
- var updater = (IBarBuildUpdater)services.GetRequiredService();
- var runner = services.GetRequiredService();
+ IBasicBarClient barClient = services.GetRequiredService();
+ IBarBuildUpdater updater = (IBarBuildUpdater)services.GetRequiredService();
+ DependencyUpdateRunner runner = services.GetRequiredService();
Build build = await barClient.GetBuildAsync(options.Id).WaitAsync(cancellationToken);
DependencyUpdate update = await updater.ResolveFromBarBuildAsync(build, cancellationToken);
diff --git a/eng/update-dependencies/Commands/FromBuildOptions.cs b/eng/update-dependencies/Commands/FromBuildOptions.cs
index 660a36ab16..4753746e57 100644
--- a/eng/update-dependencies/Commands/FromBuildOptions.cs
+++ b/eng/update-dependencies/Commands/FromBuildOptions.cs
@@ -9,14 +9,14 @@ internal record FromBuildOptions : CreatePullRequestOptions
{
public required int Id { get; init; }
- private static readonly PositiveIdArgument s_id = new("The BAR build ID to use as the update source");
+ private static readonly PositiveIdArgument IdArgument = new("The BAR build ID to use as the update source");
public static new void AddTo(Command command)
{
CreatePullRequestOptions.AddTo(command);
- command.Arguments.Add(s_id);
+ command.Arguments.Add(IdArgument);
}
public static new FromBuildOptions Bind(ParseResult result) =>
- Bind(result, new FromBuildOptions { Id = result.GetValue(s_id) });
+ Bind(result, new FromBuildOptions { Id = result.GetValue(IdArgument) });
}
diff --git a/eng/update-dependencies/Commands/FromChannelCommand.cs b/eng/update-dependencies/Commands/FromChannelCommand.cs
index 0a650e4bdd..8029c962bd 100644
--- a/eng/update-dependencies/Commands/FromChannelCommand.cs
+++ b/eng/update-dependencies/Commands/FromChannelCommand.cs
@@ -11,14 +11,14 @@ internal static class FromChannelCommand
{
public static Command CreateCliCommand(IServiceProvider services) where TUpdater : class, IUpdater
{
- var command = new Command("channel", "Update from the latest build in a BAR channel");
+ Command command = new Command("channel", "Update from the latest build in a BAR channel");
FromChannelOptions.AddTo(command);
command.SetAction(async (result, cancellationToken) =>
{
FromChannelOptions options = FromChannelOptions.Bind(result);
- var updater = (IBarChannelUpdater)services.GetRequiredService();
- var runner = services.GetRequiredService();
+ IBarChannelUpdater updater = (IBarChannelUpdater)services.GetRequiredService();
+ DependencyUpdateRunner runner = services.GetRequiredService();
DependencyUpdate update = await updater.ResolveFromBarChannelAsync(options.Channel, cancellationToken);
await runner.RunAsync(options, TUpdater.VersionSourceName, update, cancellationToken);
diff --git a/eng/update-dependencies/Commands/FromChannelOptions.cs b/eng/update-dependencies/Commands/FromChannelOptions.cs
index 34b4f540cb..abf80799be 100644
--- a/eng/update-dependencies/Commands/FromChannelOptions.cs
+++ b/eng/update-dependencies/Commands/FromChannelOptions.cs
@@ -9,14 +9,14 @@ internal record FromChannelOptions : CreatePullRequestOptions
{
public required int Channel { get; init; }
- private static readonly PositiveIdArgument s_channel = new("The BAR channel ID to use as the update source");
+ private static readonly PositiveIdArgument ChannelArgument = new("The BAR channel ID to use as the update source");
public static new void AddTo(Command command)
{
CreatePullRequestOptions.AddTo(command);
- command.Arguments.Add(s_channel);
+ command.Arguments.Add(ChannelArgument);
}
public static new FromChannelOptions Bind(ParseResult result) =>
- Bind(result, new FromChannelOptions { Channel = result.GetValue(s_channel) });
+ Bind(result, new FromChannelOptions { Channel = result.GetValue(ChannelArgument) });
}
diff --git a/eng/update-dependencies/Commands/FromGitHubReleaseCommand.cs b/eng/update-dependencies/Commands/FromGitHubReleaseCommand.cs
index ce6abbbbc8..d4e2af83dd 100644
--- a/eng/update-dependencies/Commands/FromGitHubReleaseCommand.cs
+++ b/eng/update-dependencies/Commands/FromGitHubReleaseCommand.cs
@@ -17,8 +17,8 @@ public static void Configure(Command command, IServiceProvider service
command.SetAction(async (result, cancellationToken) =>
{
CreatePullRequestOptions options = CreatePullRequestOptions.Bind(result);
- var updater = (IGitHubReleaseUpdater)services.GetRequiredService();
- var runner = services.GetRequiredService();
+ IGitHubReleaseUpdater updater = (IGitHubReleaseUpdater)services.GetRequiredService();
+ DependencyUpdateRunner runner = services.GetRequiredService();
DependencyUpdate update = await updater.ResolveFromGitHubReleaseAsync(cancellationToken);
await runner.RunAsync(options, TUpdater.VersionSourceName, update, cancellationToken);
diff --git a/eng/update-dependencies/Commands/FromStagingPipelineCommand.cs b/eng/update-dependencies/Commands/FromStagingPipelineCommand.cs
index 093a3fa345..f6874ffcb7 100644
--- a/eng/update-dependencies/Commands/FromStagingPipelineCommand.cs
+++ b/eng/update-dependencies/Commands/FromStagingPipelineCommand.cs
@@ -2,10 +2,11 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.CommandLine;
+using Microsoft.DotNet.Docker.Shared;
using Microsoft.DotNet.Docker.UpdateDependencies.Git;
+using Microsoft.DotNet.Docker.UpdateDependencies.Model.Release;
using Microsoft.DotNet.Docker.UpdateDependencies.Sync;
using Microsoft.DotNet.Docker.UpdateDependencies.Updaters;
-using Microsoft.DotNet.Docker.Shared;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -62,7 +63,7 @@ public FromStagingPipelineCommand(
public static Command CreateCliCommand(IServiceProvider services)
{
- var command = new Command("staging-pipeline", "Update .NET from staging pipeline runs");
+ Command command = new Command("staging-pipeline", "Update .NET from staging pipeline runs");
FromStagingPipelineOptions.AddTo(command);
command.SetAction((result, _) =>
{
@@ -74,7 +75,7 @@ public static Command CreateCliCommand(IServiceProvider services)
public async Task ExecuteAsync(FromStagingPipelineOptions options)
{
- var stageContainers = options.GetStageContainerList();
+ IReadOnlyList stageContainers = options.GetStageContainerList();
if (stageContainers.Count == 0)
{
@@ -91,17 +92,17 @@ public async Task ExecuteAsync(FromStagingPipelineOptions options)
// passed in, we may or may not want to actually perform git operations. GitRepoContext
// decides what git operations to perform and tells us where to make changes. This keeps
// all the git-related logic in one place.
- var gitRepoContext = await _createGitRepoContextAsync(options);
+ GitRepoContext gitRepoContext = await _createGitRepoContextAsync(options);
List commitMessages = [];
List prBodySections = [];
// Process each stage container, creating a separate commit for each
- foreach (var stageContainer in stageContainers)
+ foreach (string stageContainer in stageContainers)
{
_logger.LogInformation("Processing stage container: {StageContainer}", stageContainer);
- var (commitMessage, prBodySection) = await ProcessStageContainerAsync(
+ (string? commitMessage, string? prBodySection) = await ProcessStageContainerAsync(
options,
stageContainer,
gitRepoContext);
@@ -114,11 +115,11 @@ public async Task ExecuteAsync(FromStagingPipelineOptions options)
}
// Create pull request with all commits
- var prTitle = stageContainers.Count == 1
+ string prTitle = stageContainers.Count == 1
? $"[{options.TargetBranch}] {commitMessages[0]}"
: $"[{options.TargetBranch}] Update .NET dependencies from {stageContainers.Count} stage containers";
- var prBody = string.Join(Environment.NewLine + Environment.NewLine, prBodySections);
+ string prBody = string.Join(Environment.NewLine + Environment.NewLine, prBodySections);
await gitRepoContext.PushAndCreatePullRequest(prTitle, prBody);
return 0;
@@ -135,10 +136,10 @@ public async Task ExecuteAsync(FromStagingPipelineOptions options)
string stageContainer,
GitRepoContext gitRepoContext)
{
- var stagingPipelineRunId = StagingPipelineOptionsExtensions.GetStagingPipelineRunId(stageContainer);
+ int stagingPipelineRunId = StagingPipelineOptionsExtensions.GetStagingPipelineRunId(stageContainer);
// Log staging pipeline tags for diagnostic purposes
- var stagingPipelineTags = await _pipelinesService.GetBuildTagsAsync(
+ IReadOnlyList stagingPipelineTags = await _pipelinesService.GetBuildTagsAsync(
_configuration.AzureDevOps.Organization,
_configuration.AzureDevOps.Project,
stagingPipelineRunId);
@@ -160,7 +161,7 @@ public async Task ExecuteAsync(FromStagingPipelineOptions options)
+ $"/{stageContainer}/assets/shipping/assets";
}
- var releaseConfig = await _pipelineArtifactProvider.GetReleaseConfigAsync(
+ ReleaseConfig releaseConfig = await _pipelineArtifactProvider.GetReleaseConfigAsync(
_configuration.AzureDevOps.Organization,
_configuration.AzureDevOps.Project,
stagingPipelineRunId);
@@ -183,7 +184,7 @@ public async Task ExecuteAsync(FromStagingPipelineOptions options)
string? aspnetVersion = options.Internal ? releaseConfig.AspBuild : releaseConfig.Asp;
// Example build URL: https://dev.azure.com///_build/results?buildId=
- var buildUrl = $"{_configuration.AzureDevOps.Organization.TrimEnd('/')}/{_configuration.AzureDevOps.Project}/_build/results?buildId={stagingPipelineRunId}";
+ string buildUrl = $"{_configuration.AzureDevOps.Organization.TrimEnd('/')}/{_configuration.AzureDevOps.Project}/_build/results?buildId={stagingPipelineRunId}";
_logger.LogInformation(
"Applying internal build {StageContainer} ({BuildUrl})",
stageContainer, buildUrl);
@@ -194,7 +195,7 @@ await DependencyUpdateRunner.ApplyAsync(
variables, repoRoot, releaseConfig, internalBaseUrl, token),
CancellationToken.None);
- var commitMessage = releaseConfig switch
+ string commitMessage = releaseConfig switch
{
{ SdkOnly: true } => $"Update .NET {majorMinorVersionString} SDK to {sdkVersion}",
_ => $"Update .NET {majorMinorVersionString} to {sdkVersion} SDK / {runtimeVersion} Runtime",
@@ -208,8 +209,9 @@ await DependencyUpdateRunner.ApplyAsync(
newVersionsList.Add($"- ASPNET: {aspnetVersion}");
newVersionsList.Add($"- ASPNET-COMPOSITE: {aspnetVersion}");
}
+
newVersionsList.Add($"- SDK: {sdkVersion}");
- var prBodySection = $"""
+ string prBodySection = $"""
## .NET {majorMinorVersionString}
This updates .NET {majorMinorVersionString} to the following versions:
@@ -280,21 +282,22 @@ public static async Task CreateAsync(
if (options.SubmitPullRequest)
{
- var remoteUrl = configuration.AzureDevOps.GetRepoUrl();
- var targetBranch = options.TargetBranch;
- var buildId = environmentService.GetBuildId() ?? "";
- var stageContainerList = options.GetStageContainerList();
+ string remoteUrl = configuration.AzureDevOps.GetRepoUrl();
+ string targetBranch = options.TargetBranch;
+ string buildId = environmentService.GetBuildId() ?? "";
+ IReadOnlyList stageContainerList = options.GetStageContainerList();
if (stageContainerList.Count == 0)
{
throw new ArgumentException("At least one stage container must be provided.");
}
- var prBranch = options.CreatePullRequestBranchName(
+
+ string prBranch = options.CreatePullRequestBranchName(
$"update-deps-int-{stageContainerList[0]}",
buildId);
- var committer = configuration.GetCommitterIdentity();
+ (string Name, string Email) committer = configuration.GetCommitterIdentity();
// Clone the repo and configure git identity for commits
- var git = await gitRepoFactory.CreateAndCloneAsync(remoteUrl, gitIdentity: committer);
+ IGitRepoHelper git = await gitRepoFactory.CreateAndCloneAsync(remoteUrl, gitIdentity: committer);
// Ensure the branch we want to modify exists, then check it out
await git.Remote.EnsureBranchExistsAsync(targetBranch);
// Create a new branch to push changes to and create a PR from
@@ -325,17 +328,19 @@ await git.Remote.CreatePullRequestAsync(new(
logger.LogInformation("No git operations will be performed.");
localRepoPath = options.RepoRoot;
- commitChanges = async (commitMessage) =>
+ commitChanges = (commitMessage) =>
{
logger.LogInformation("Skipping commit.");
logger.LogInformation("Commit message: {CommitMessage}", commitMessage);
+ return Task.CompletedTask;
};
- pushAndCreatePullRequest = async (prTitle, prBody) =>
+ pushAndCreatePullRequest = (prTitle, prBody) =>
{
logger.LogInformation("Skipping push and pull request creation.");
logger.LogInformation("Pull request title: {PullRequestTitle}", prTitle);
logger.LogInformation("Pull request body:\n{PullRequestBody}", prBody);
+ return Task.CompletedTask;
};
}
diff --git a/eng/update-dependencies/Commands/FromStagingPipelineOptions.cs b/eng/update-dependencies/Commands/FromStagingPipelineOptions.cs
index 28f0b3099c..83145a6c5e 100644
--- a/eng/update-dependencies/Commands/FromStagingPipelineOptions.cs
+++ b/eng/update-dependencies/Commands/FromStagingPipelineOptions.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.CommandLine;
+using System.Globalization;
using System.Text.RegularExpressions;
namespace Microsoft.DotNet.Docker.UpdateDependencies.Commands;
@@ -20,7 +21,7 @@ internal partial record FromStagingPipelineOptions : CreatePullRequestOptions
///
/// Whether or not to use the internal versions of the staged build.
///
- public bool Internal { get; init; } = false;
+ public bool Internal { get; init; }
///
/// This Azure Storage Account will be used as a source for the update.
@@ -28,7 +29,7 @@ internal partial record FromStagingPipelineOptions : CreatePullRequestOptions
///
public string? StagingStorageAccount { get; init; }
- private static readonly Argument s_stageContainers = new("stage-containers")
+ private static readonly Argument StageContainersArgument = new("stage-containers")
{
Description = "A comma-delimited list of stage container names to use as a source for the update (e.g., 'stage-1234567,stage-2345678')",
};
@@ -39,7 +40,7 @@ internal partial record FromStagingPipelineOptions : CreatePullRequestOptions
+ " For example: https://dotnetstagetest.blob.core.windows.net/",
DefaultValueFactory = _ => string.Empty,
};
- private static readonly Option s_internal = new(InternalOption)
+ private static readonly Option UseInternalOption = new(InternalOption)
{
Description = "Whether or not to use the internal versions of the staged build. When not using an internal"
+ " build, Dockerfiles will be updated as if the staged build has already been released. When using an"
@@ -50,17 +51,17 @@ internal partial record FromStagingPipelineOptions : CreatePullRequestOptions
public static new void AddTo(Command command)
{
CreatePullRequestOptions.AddTo(command);
- command.Arguments.Add(s_stageContainers);
+ command.Arguments.Add(StageContainersArgument);
command.Options.Add(StagingStorageAccountOption);
- command.Options.Add(s_internal);
+ command.Options.Add(UseInternalOption);
}
public static new FromStagingPipelineOptions Bind(ParseResult result) =>
Bind(result, new FromStagingPipelineOptions
{
- StageContainers = result.GetRequiredValue(s_stageContainers),
+ StageContainers = result.GetRequiredValue(StageContainersArgument),
StagingStorageAccount = result.GetRequiredValue(StagingStorageAccountOption),
- Internal = result.GetValue(s_internal),
+ Internal = result.GetValue(UseInternalOption),
});
///
@@ -87,12 +88,13 @@ internal static partial class StagingPipelineOptionsExtensions
///
public static int GetStagingPipelineRunId(string stageContainer)
{
- var match = StageContainerRegex.Match(stageContainer);
+ Match match = StageContainerRegex.Match(stageContainer);
if (!match.Success)
{
throw new ArgumentException(
$"Invalid stage container name '{stageContainer}'. Expected format: 'stage-{{buildId}}' (e.g., 'stage-1234567')");
}
- return int.Parse(match.Groups[1].Value);
+
+ return int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
}
}
diff --git a/eng/update-dependencies/Commands/MonitorCommand.cs b/eng/update-dependencies/Commands/MonitorCommand.cs
index 14d9a1a0cb..b831602c3d 100644
--- a/eng/update-dependencies/Commands/MonitorCommand.cs
+++ b/eng/update-dependencies/Commands/MonitorCommand.cs
@@ -20,9 +20,9 @@ public static Command CreateCliCommand(IServiceProvider services) =>
private static Command CreatePipelineBuildCommand(IServiceProvider services)
{
- var runId = new PositiveIdArgument("The Azure DevOps pipeline run ID to use as the update source");
+ PositiveIdArgument runId = new PositiveIdArgument("The Azure DevOps pipeline run ID to use as the update source");
- var command = new Command("pipeline-build", "Update from an Azure DevOps pipeline run")
+ Command command = new Command("pipeline-build", "Update from an Azure DevOps pipeline run")
{
runId
};
@@ -32,8 +32,8 @@ private static Command CreatePipelineBuildCommand(IServiceProvider services)
command.SetAction(async (parseResult, cancellationToken) =>
{
CreatePullRequestOptions options = CreatePullRequestOptions.Bind(parseResult);
- var updater = services.GetRequiredService();
- var runner = services.GetRequiredService();
+ MonitorUpdater updater = services.GetRequiredService();
+ DependencyUpdateRunner runner = services.GetRequiredService();
DependencyUpdate update = await updater.ResolveFromPipelineBuildAsync(
parseResult.GetValue(runId),
@@ -47,12 +47,12 @@ private static Command CreatePipelineBuildCommand(IServiceProvider services)
private static Command CreateVersionCommand(IServiceProvider services)
{
- var version = new Argument("version")
+ Argument version = new Argument("version")
{
Description = "The product version to use as the update source",
};
- var command = new Command("version", "Update to a specific version")
+ Command command = new Command("version", "Update to a specific version")
{
version
};
@@ -62,8 +62,8 @@ private static Command CreateVersionCommand(IServiceProvider services)
command.SetAction(async (parseResult, cancellationToken) =>
{
CreatePullRequestOptions options = CreatePullRequestOptions.Bind(parseResult);
- var updater = services.GetRequiredService();
- var runner = services.GetRequiredService();
+ MonitorUpdater updater = services.GetRequiredService();
+ DependencyUpdateRunner runner = services.GetRequiredService();
DependencyUpdate update = updater.ResolveFromVersion(parseResult.GetRequiredValue(version));
diff --git a/eng/update-dependencies/Commands/PowerShellCommand.cs b/eng/update-dependencies/Commands/PowerShellCommand.cs
index 4a53d672a4..6a4c79764a 100644
--- a/eng/update-dependencies/Commands/PowerShellCommand.cs
+++ b/eng/update-dependencies/Commands/PowerShellCommand.cs
@@ -11,12 +11,12 @@ internal static class PowerShellCommand
{
private const string VersionSourceName = "PowerShell/PowerShell";
- private static readonly Option s_internal = new("--internal")
+ private static readonly Option Internal = new("--internal")
{
Description = "Use the internal (non-public) location for the version",
};
- private static readonly Option s_dotnetVersion = new("--dotnet-version")
+ private static readonly Option DotnetVersion = new("--dotnet-version")
{
Description = "Only update images for this .NET major.minor version (for example, 9.0)",
};
@@ -30,22 +30,22 @@ public static Command CreateCliCommand(IServiceProvider services) =>
private static Command CreateVersionCommand(IServiceProvider services)
{
- var version = new Argument("version")
+ Argument version = new Argument("version")
{
Description = "The PowerShell version to update to",
};
- var baseUrl = new Option("--base-url")
+ Option baseUrl = new Option("--base-url")
{
Description = "Use this exact location for the version instead of the default public or internal one",
};
- var command = new Command("version", "Update to a specific version")
+ Command command = new Command("version", "Update to a specific version")
{
version,
baseUrl,
- s_internal,
- s_dotnetVersion,
+ Internal,
+ DotnetVersion,
};
CreatePullRequestOptions.AddTo(command);
@@ -53,14 +53,14 @@ private static Command CreateVersionCommand(IServiceProvider services)
command.SetAction(async (parseResult, cancellationToken) =>
{
CreatePullRequestOptions options = CreatePullRequestOptions.Bind(parseResult);
- var updater = services.GetRequiredService();
- var runner = services.GetRequiredService();
+ PowerShellUpdater updater = services.GetRequiredService();
+ DependencyUpdateRunner runner = services.GetRequiredService();
DependencyUpdate update = updater.ResolveFromVersion(
parseResult.GetRequiredValue(version),
- parseResult.GetValue(s_internal),
+ parseResult.GetValue(Internal),
parseResult.GetValue(baseUrl),
- parseResult.GetValue(s_dotnetVersion));
+ parseResult.GetValue(DotnetVersion));
await runner.RunAsync(options, VersionSourceName, update, cancellationToken);
});
@@ -70,10 +70,10 @@ private static Command CreateVersionCommand(IServiceProvider services)
private static Command CreateLatestCommand(IServiceProvider services)
{
- var command = new Command("latest", "Update to the latest available version")
+ Command command = new Command("latest", "Update to the latest available version")
{
- s_internal,
- s_dotnetVersion,
+ Internal,
+ DotnetVersion,
};
CreatePullRequestOptions.AddTo(command);
@@ -81,15 +81,15 @@ private static Command CreateLatestCommand(IServiceProvider services)
command.SetAction(async (parseResult, cancellationToken) =>
{
CreatePullRequestOptions options = CreatePullRequestOptions.Bind(parseResult);
- var updater = services.GetRequiredService();
- var runner = services.GetRequiredService();
+ PowerShellUpdater updater = services.GetRequiredService();
+ DependencyUpdateRunner runner = services.GetRequiredService();
ManifestVariables variables = await runner.GetTargetManifestAsync(options, cancellationToken);
IReadOnlyList updates = await updater.ResolveLatestAsync(
variables,
- parseResult.GetValue(s_internal),
- parseResult.GetValue(s_dotnetVersion),
+ parseResult.GetValue(Internal),
+ parseResult.GetValue(DotnetVersion),
cancellationToken);
foreach (DependencyUpdate update in updates)
diff --git a/eng/update-dependencies/Commands/SyncInternalReleaseCommand.cs b/eng/update-dependencies/Commands/SyncInternalReleaseCommand.cs
index f761d98170..8a9f4c78ef 100644
--- a/eng/update-dependencies/Commands/SyncInternalReleaseCommand.cs
+++ b/eng/update-dependencies/Commands/SyncInternalReleaseCommand.cs
@@ -36,7 +36,7 @@ ILogger logger
public static Command CreateCliCommand(IServiceProvider services)
{
- var command = new Command("sync-internal-release", "Sync release/* branch to internal/release/* branch");
+ Command command = new Command("sync-internal-release", "Sync release/* branch to internal/release/* branch");
SyncInternalReleaseOptions.AddTo(command);
command.SetAction((result, _) =>
{
@@ -48,7 +48,7 @@ public static Command CreateCliCommand(IServiceProvider services)
public async Task ExecuteAsync(SyncInternalReleaseOptions options)
{
- var remoteUrl = configuration.AzureDevOps.GetRepoUrl();
+ string remoteUrl = configuration.AzureDevOps.GetRepoUrl();
// Do not allow syncing starting from an internal branch.
if (options.SourceBranch.StartsWith("internal/", StringComparison.OrdinalIgnoreCase))
@@ -58,16 +58,16 @@ public async Task ExecuteAsync(SyncInternalReleaseOptions options)
}
// Get git identity if available (required for commits, optional for read-only operations)
- var gitIdentity = !string.IsNullOrWhiteSpace(configuration.User) && !string.IsNullOrWhiteSpace(configuration.Email)
+ (string, string)? gitIdentity = !string.IsNullOrWhiteSpace(configuration.User) && !string.IsNullOrWhiteSpace(configuration.Email)
? configuration.GetCommitterIdentity()
- : ((string, string)?)null;
+ : null;
- using var repo = await _gitRepoHelperFactory.CreateAndCloneAsync(
+ using IGitRepoHelper repo = await _gitRepoHelperFactory.CreateAndCloneAsync(
remoteUrl,
gitIdentity: gitIdentity);
// Verify that the source branch exists on the remote.
- var sourceBranchExists = await repo.Remote.RemoteBranchExistsAsync(options.SourceBranch);
+ bool sourceBranchExists = await repo.Remote.RemoteBranchExistsAsync(options.SourceBranch);
if (!sourceBranchExists)
{
throw new InvalidOperationException(
@@ -75,7 +75,7 @@ public async Task ExecuteAsync(SyncInternalReleaseOptions options)
}
// If the target branch doesn't exist, then create it based on the source branch.
- var targetBranchExists = await repo.Remote.RemoteBranchExistsAsync(options.TargetBranch);
+ bool targetBranchExists = await repo.Remote.RemoteBranchExistsAsync(options.TargetBranch);
if (!targetBranchExists)
{
await repo.Remote.CreateRemoteBranchAsync(
@@ -84,10 +84,10 @@ await repo.Remote.CreateRemoteBranchAsync(
return 0;
}
- var sourceSha = await repo.Remote.GetRemoteBranchShaAsync(options.SourceBranch)
- ?? throw new Exception("Failed to get source branch SHA even though branch exists on remote.");
- var targetSha = await repo.Remote.GetRemoteBranchShaAsync(options.TargetBranch)
- ?? throw new Exception("Failed to get target branch SHA even though branch exists on remote.");
+ string sourceSha = await repo.Remote.GetRemoteBranchShaAsync(options.SourceBranch)
+ ?? throw new InvalidOperationException("Failed to get source branch SHA even though branch exists on remote.");
+ string targetSha = await repo.Remote.GetRemoteBranchShaAsync(options.TargetBranch)
+ ?? throw new InvalidOperationException("Failed to get target branch SHA even though branch exists on remote.");
// If both branches are at the same commit, then there is nothing to do.
if (sourceSha == targetSha)
@@ -105,11 +105,11 @@ await repo.Remote.CreateRemoteBranchAsync(
// We haven't pulled either branch into our working tree yet, so we
// reference them with the usual "refs/heads/..." prefix. Assume that
// both branches exist on the "origin" remote.
- var targetIsAncestorOfSource = await repo.Local.IsAncestorAsync(
+ bool targetIsAncestorOfSource = await repo.Local.IsAncestorAsync(
ancestorRef: $"origin/{options.TargetBranch}",
descendantRef: $"origin/{options.SourceBranch}");
- var buildId = _environmentService.GetBuildId() ?? "";
+ string buildId = _environmentService.GetBuildId() ?? "";
if (targetIsAncestorOfSource)
{
@@ -118,7 +118,7 @@ await repo.Remote.CreateRemoteBranchAsync(
options.TargetBranch, options.SourceBranch);
// "ff" here is an abbreviation for "fast-forward" - just want to keep branch names short
- var fastForwardPrBranch = options.CreatePullRequestBranchName(
+ string fastForwardPrBranch = options.CreatePullRequestBranchName(
"ff",
buildId);
await repo.Remote.CreateRemoteBranchAsync(
@@ -153,16 +153,16 @@ await repo.Remote.CreatePullRequestAsync(new(
// At this point, we need to verify that we have everything we need to
// access internal builds, commit changes, and submit a pull request.
ArgumentException.ThrowIfNullOrWhiteSpace(options.StagingStorageAccount);
- var commitAuthor = configuration.GetCommitterIdentity();
+ (string Name, string Email) commitAuthor = configuration.GetCommitterIdentity();
// Checkout both branches locally so that we can work with them directly.
await repo.CheckoutRemoteBranchAsync(options.SourceBranch);
await repo.CheckoutRemoteBranchAsync(options.TargetBranch);
- var internalBuilds = _internalVersionsService.GetInternalStagingBuilds(repo.Local.LocalPath);
+ InternalStageContainers internalBuilds = _internalVersionsService.GetInternalStagingBuilds(repo.Local.LocalPath);
// Reset the target branch to match the source branch.
- var prBranchName = options.CreatePullRequestBranchName(
+ string prBranchName = options.CreatePullRequestBranchName(
name: "sync",
buildId: buildId);
await repo.Local.CreateAndCheckoutLocalBranchAsync(prBranchName);
@@ -173,10 +173,9 @@ await repo.Local.CommitAsync(
author: commitAuthor);
// Re-apply internal .NET version updates for each recorded stage container.
- foreach (var (dockerfileVersion, stageContainer) in internalBuilds.Versions)
+ foreach ((Shared.DotNetVersion? dockerfileVersion, string? stageContainer) in internalBuilds.Versions)
{
await ApplyInternalBuildAsync(
- options: options,
localRepo: repo.Local,
stageContainer: stageContainer,
stagingStorageAccount: options.StagingStorageAccount,
@@ -198,14 +197,15 @@ await repo.Remote.CreatePullRequestAsync(new(
///
/// Apply an internal build by invoking the FromStagingPipelineCommand.
///
+ /// The local repository to update.
///
/// Stage container name (e.g., "stage-1234567") to get build information from.
///
+ /// The storage account containing the staged build.
///
/// The identity to use when committing changes.
///
private async Task ApplyInternalBuildAsync(
- SyncInternalReleaseOptions options,
ILocalGitRepoHelper localRepo,
string stageContainer,
string stagingStorageAccount,
@@ -215,7 +215,7 @@ private async Task ApplyInternalBuildAsync(
"Ignore any git-related logging output below, because git "
+ "operations are being managed by a different command.");
- var fromStagingPipelineOptions = new FromStagingPipelineOptions
+ FromStagingPipelineOptions fromStagingPipelineOptions = new FromStagingPipelineOptions
{
RepoRoot = localRepo.LocalPath,
Internal = true,
@@ -223,7 +223,7 @@ private async Task ApplyInternalBuildAsync(
StagingStorageAccount = stagingStorageAccount,
};
- var exitCode = await _updateFromStagingPipeline.ExecuteAsync(fromStagingPipelineOptions);
+ int exitCode = await _updateFromStagingPipeline.ExecuteAsync(fromStagingPipelineOptions);
if (exitCode != 0)
{
throw new InvalidOperationException(
diff --git a/eng/update-dependencies/DependencyUpdateRunner.cs b/eng/update-dependencies/DependencyUpdateRunner.cs
index e8a72f9c97..059e53789f 100644
--- a/eng/update-dependencies/DependencyUpdateRunner.cs
+++ b/eng/update-dependencies/DependencyUpdateRunner.cs
@@ -1,5 +1,5 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using Azure.Identity;
@@ -42,14 +42,14 @@ public async Task RunAsync(
branchSuffix = branchSuffix.Replace('/', '-');
string branchName = $"{options.TargetBranch}-{branchSuffix}";
- var definition = new PullRequestDefinition(
+ PullRequestDefinition definition = new PullRequestDefinition(
Key: branchName,
Title: title,
Body: string.Empty,
TargetBranch: options.TargetBranch,
ApplyChanges: (git, token) => ApplyAsync(git.WorkspaceDirectory, update.ApplyAsync, token));
- var manager = new PullRequestManager(loggerFactory);
+ PullRequestManager manager = new PullRequestManager(loggerFactory);
PullRequestResult result = await manager.CreateOrUpdateAsync(
definition,
CreatePullRequestEndpoint(configuration),
@@ -72,9 +72,9 @@ public async Task GetTargetManifestAsync(
// This clone is only read. PullRequestManager makes its own clone when it applies updates.
IPullRequestEndpoint endpoint = CreatePullRequestEndpoint(configuration);
- var processRunner = new ProcessRunner(loggerFactory.CreateLogger());
+ ProcessRunner processRunner = new ProcessRunner(loggerFactory.CreateLogger());
- using var workingCopy = new GitWorkingCopy(processRunner, loggerFactory.CreateLogger());
+ using GitWorkingCopy workingCopy = new GitWorkingCopy(processRunner, loggerFactory.CreateLogger());
AutomationIdentity identity = await endpoint.GetIdentityAsync(cancellationToken);
await endpoint.CloneTargetAsync(workingCopy, options.TargetBranch, identity, cancellationToken);
@@ -89,7 +89,7 @@ public static async Task ApplyAsync(
{
repoRoot = Path.GetFullPath(repoRoot);
string manifestPath = GetManifestPath(repoRoot);
- var variables = ManifestVariables.FromFile(manifestPath);
+ ManifestVariables variables = ManifestVariables.FromFile(manifestPath);
string originalContent = variables.Content;
await applyUpdate(variables, repoRoot, cancellationToken);
@@ -117,16 +117,16 @@ private static IPullRequestEndpoint CreatePullRequestEndpoint(
private static GitHubPullRequestEndpoint CreateGitHubPullRequestEndpoint(
UpdateDependenciesConfiguration configuration)
{
- var github = configuration.GitHub;
- var identity = new AutomationIdentity(configuration.User, configuration.Email);
- var repo = new GitHubRepo(github.Owner, github.Repository);
+ GitHubConfiguration github = configuration.GitHub;
+ AutomationIdentity identity = new AutomationIdentity(configuration.User, configuration.Email);
+ GitHubRepo repo = new GitHubRepo(github.Owner, github.Repository);
IGitHubAccessProvider accessProvider;
if (!string.IsNullOrEmpty(github.AppClientId)
&& !string.IsNullOrEmpty(github.AppKeyUri))
{
- var credential = new AzureCliCredential();
- var cryptographyClient = new CryptographyClient(new Uri(github.AppKeyUri), credential);
+ AzureCliCredential credential = new AzureCliCredential();
+ CryptographyClient cryptographyClient = new CryptographyClient(new Uri(github.AppKeyUri), credential);
accessProvider = new GitHubAppAccessProvider(github.AppClientId, cryptographyClient);
}
else
@@ -140,13 +140,13 @@ private static GitHubPullRequestEndpoint CreateGitHubPullRequestEndpoint(
private static AzureDevOpsPullRequestEndpoint CreateAzureDevOpsPullRequestEndpoint(
UpdateDependenciesConfiguration configuration)
{
- var azureDevOps = configuration.AzureDevOps;
+ AzureDevOpsConfiguration azureDevOps = configuration.AzureDevOps;
string organization = new Uri(azureDevOps.Organization).Segments.Last().TrimEnd('/');
- var client = new AzureDevOpsClient(
+ AzureDevOpsClient client = new AzureDevOpsClient(
organization,
azureDevOps.Project,
azureDevOps.Token);
- var identity = new AutomationIdentity(configuration.User, configuration.Email);
+ AutomationIdentity identity = new AutomationIdentity(configuration.User, configuration.Email);
return new AzureDevOpsPullRequestEndpoint(
client,
diff --git a/eng/update-dependencies/DirectoryStack.cs b/eng/update-dependencies/DirectoryStack.cs
index 71f614caa6..2caeeea07f 100644
--- a/eng/update-dependencies/DirectoryStack.cs
+++ b/eng/update-dependencies/DirectoryStack.cs
@@ -1,5 +1,5 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.DotNet.Docker.UpdateDependencies;
@@ -25,7 +25,7 @@ internal static class DirectoryStack
///
private sealed class DirectoryContext : IDisposable
{
- private static readonly Stack s_directoryStack = new();
+ private static readonly Stack DirectoryStack = new();
private bool _disposed;
@@ -51,7 +51,7 @@ public void Dispose()
///
private static void Push(string path)
{
- s_directoryStack.Push(Directory.GetCurrentDirectory());
+ DirectoryStack.Push(Directory.GetCurrentDirectory());
Directory.SetCurrentDirectory(path);
}
@@ -62,9 +62,9 @@ private static void Push(string path)
///
private static void Pop()
{
- if (s_directoryStack.Count > 0)
+ if (DirectoryStack.Count > 0)
{
- string previousDirectory = s_directoryStack.Pop();
+ string previousDirectory = DirectoryStack.Pop();
Directory.SetCurrentDirectory(previousDirectory);
}
else
diff --git a/eng/update-dependencies/Git/GitRepoHelper.cs b/eng/update-dependencies/Git/GitRepoHelper.cs
index 796d3d5ce9..f3275f7714 100644
--- a/eng/update-dependencies/Git/GitRepoHelper.cs
+++ b/eng/update-dependencies/Git/GitRepoHelper.cs
@@ -38,14 +38,14 @@ ILogger logger
///
public async Task CheckoutRemoteBranchAsync(string branchName)
{
- var currentBranch = await Local.GetCurrentBranchAsync();
+ string currentBranch = await Local.GetCurrentBranchAsync();
if (currentBranch == branchName)
{
_logger.LogInformation("Already on branch {BranchName}", branchName);
return;
}
- var branchExists = await Remote.RemoteBranchExistsAsync(branchName);
+ bool branchExists = await Remote.RemoteBranchExistsAsync(branchName);
if (!branchExists)
{
throw new InvalidBranchException($"Branch '{branchName}' does not exist on remote.");
@@ -58,8 +58,8 @@ public async Task CheckoutRemoteBranchAsync(string branchName)
public async Task PushLocalBranchAsync(string branchName)
{
// Get all remotes and find the one that matches the target repo.
- var remotes = await Local.ListAllRemotesAsync();
- var targetRemote = remotes.First(remote => remote.Url == _repoUri);
+ IEnumerable remotes = await Local.ListAllRemotesAsync();
+ GitRemoteInfo targetRemote = remotes.First(remote => remote.Url == _repoUri);
_logger.LogInformation("Pushing branch {BranchName} to remote {RemoteUrl}", branchName, targetRemote.Url);
await _libGit2Client.Push(Local.LocalPath, branchName, targetRemote.Url);
diff --git a/eng/update-dependencies/Git/GitRepoHelperFactory.cs b/eng/update-dependencies/Git/GitRepoHelperFactory.cs
index 9a4ce5bca8..430e6a3c2a 100644
--- a/eng/update-dependencies/Git/GitRepoHelperFactory.cs
+++ b/eng/update-dependencies/Git/GitRepoHelperFactory.cs
@@ -77,11 +77,11 @@ await _gitRepoCloner.CloneAsync(
gitDirectory: null);
_logger.LogInformation("Cloned '{RepoUri}' to '{LocalCloneDir}'", repoUri, localCloneDir);
- var gitRepoHelper = CreateFromLocal(repoUri, localCloneDir);
+ GitRepoHelper gitRepoHelper = CreateFromLocal(repoUri, localCloneDir);
if (gitIdentity is { } identity)
{
- var localGitRepo = _localGitRepoFactory.Create(new NativePath(localCloneDir));
+ ILocalGitRepo localGitRepo = _localGitRepoFactory.Create(new NativePath(localCloneDir));
await localGitRepo.SetConfigValue("user.name", identity.Name);
await localGitRepo.SetConfigValue("user.email", identity.Email);
_logger.LogInformation(
@@ -97,13 +97,13 @@ public GitRepoHelper CreateFromLocal(string repoUri, string? localPath = null)
{
localPath ??= Directory.GetCurrentDirectory();
- var localGitRepo = _localGitRepoFactory.Create(new NativePath(localPath));
- var localGitRepoHelper = new LocalGitRepoHelper(
+ ILocalGitRepo localGitRepo = _localGitRepoFactory.Create(new NativePath(localPath));
+ LocalGitRepoHelper localGitRepoHelper = new LocalGitRepoHelper(
localPath: localPath,
localGitRepo: localGitRepo,
logger: _serviceProvider.GetRequiredService>());
- var remoteGitRepoHelper = new RemoteGitRepoHelper(
+ RemoteGitRepoHelper remoteGitRepoHelper = new RemoteGitRepoHelper(
repoUri,
_remoteFactory.CreateRemoteGitRepo(repoUri),
localGitRepo: localGitRepo,
diff --git a/eng/update-dependencies/Git/GitValidationExtensions.cs b/eng/update-dependencies/Git/GitValidationExtensions.cs
index 82b823b7e3..6ba010d0e7 100644
--- a/eng/update-dependencies/Git/GitValidationExtensions.cs
+++ b/eng/update-dependencies/Git/GitValidationExtensions.cs
@@ -7,7 +7,7 @@ internal static class GitValidationExtensions
{
public static async Task EnsureBranchExistsAsync(this IRemoteGitRepoHelper remote, string branchName)
{
- var branch = await remote.RemoteBranchExistsAsync(branchName);
+ bool branch = await remote.RemoteBranchExistsAsync(branchName);
if (!branch)
{
throw new InvalidOperationException(
diff --git a/eng/update-dependencies/Git/IRemoteGitRepoHelper.cs b/eng/update-dependencies/Git/IRemoteGitRepoHelper.cs
index cedeca2cac..eaf75cfd0f 100644
--- a/eng/update-dependencies/Git/IRemoteGitRepoHelper.cs
+++ b/eng/update-dependencies/Git/IRemoteGitRepoHelper.cs
@@ -18,8 +18,8 @@ internal interface IRemoteGitRepoHelper
Task RemoteBranchExistsAsync(string branchName);
///
- /// Create a new branch in the remote repository. The
- /// will be based off of the .
+ /// Create a new branch in the remote repository. The
+ /// will be based off of the .
///
///
/// This branch will be created on the remote repository.
diff --git a/eng/update-dependencies/Git/LocalGitRepoHelper.cs b/eng/update-dependencies/Git/LocalGitRepoHelper.cs
index ca8ed6a674..fe8540f877 100644
--- a/eng/update-dependencies/Git/LocalGitRepoHelper.cs
+++ b/eng/update-dependencies/Git/LocalGitRepoHelper.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.DotNet.DarcLib;
+using Microsoft.DotNet.DarcLib.Helpers;
using Microsoft.Extensions.Logging;
namespace Microsoft.DotNet.Docker.UpdateDependencies.Git;
@@ -40,7 +41,7 @@ public Task CheckoutRefAsync(string gitRef)
{
try
{
- var result = await _localGitRepo.GetShaForRefAsync(gitRef);
+ string result = await _localGitRepo.GetShaForRefAsync(gitRef);
return result;
}
catch
@@ -50,28 +51,25 @@ public Task CheckoutRefAsync(string gitRef)
}
///
- public async Task StageAsync(params IEnumerable paths)
- {
- await _localGitRepo.StageAsync(paths);
- }
+ public async Task StageAsync(params IEnumerable paths) => await _localGitRepo.StageAsync(paths);
///
public async Task CommitAsync(string message, (string Name, string Email) author)
{
- var gitStatusLines = await _localGitRepo.RunGitCommandAsync(["status"]);
- var gitStatus = string.Join(Environment.NewLine, gitStatusLines);
+ ProcessExecutionResult gitStatusLines = await _localGitRepo.RunGitCommandAsync(["status"]);
+ string gitStatus = string.Join(Environment.NewLine, gitStatusLines);
_logger.LogInformation(
"""
Git status:
- {gitStatus}
+ {GitStatus}
""",
gitStatus);
await _localGitRepo.CommitAsync(message, allowEmpty: false, author);
- var commitSha = await _localGitRepo.GetShaForRefAsync("HEAD");
+ string commitSha = await _localGitRepo.GetShaForRefAsync("HEAD");
_logger.LogInformation(
- "Created commit {commitSha}, message: '{commitMessage}' ({authorName} <{authorEmail}>)",
+ "Created commit {CommitSha}, message: '{CommitMessage}' ({AuthorName} <{AuthorEmail}>)",
commitSha, message, author.Name, author.Email);
return commitSha;
@@ -80,22 +78,22 @@ public async Task CommitAsync(string message, (string Name, string Email
///
public async Task IsAncestorAsync(string ancestorRef, string descendantRef)
{
- var ancestorCommit = await GetShaForRefAsync(ancestorRef);
- var descendantCommit = await GetShaForRefAsync(descendantRef);
+ string? ancestorCommit = await GetShaForRefAsync(ancestorRef);
+ string? descendantCommit = await GetShaForRefAsync(descendantRef);
if (ancestorCommit is null || descendantCommit is null)
{
return false;
}
- var isAncestor = await _localGitRepo.IsAncestorCommit(ancestorCommit, descendantCommit);
+ bool isAncestor = await _localGitRepo.IsAncestorCommit(ancestorCommit, descendantCommit);
return isAncestor;
}
///
public async Task> ListAllRemotesAsync()
{
- var remotesOutput = await _localGitRepo.ExecuteGitCommand("remote", "-v");
+ ProcessExecutionResult remotesOutput = await _localGitRepo.ExecuteGitCommand("remote", "-v");
/* Example output of `git remote -v`:
origin https://github.com/dotnet/runtime (fetch)
diff --git a/eng/update-dependencies/Git/RemoteGitRepoFactory.cs b/eng/update-dependencies/Git/RemoteGitRepoFactory.cs
index ef5d22b26d..ff44acf1d9 100644
--- a/eng/update-dependencies/Git/RemoteGitRepoFactory.cs
+++ b/eng/update-dependencies/Git/RemoteGitRepoFactory.cs
@@ -27,7 +27,7 @@ internal sealed class RemoteGitRepoFactory(IServiceProvider serviceProvider) : I
///
public IRemoteGitRepo CreateRemoteGitRepo(string remoteUrl)
{
- var gitRemoteKind = ParseGitRemoteFromUrl(remoteUrl);
+ GitRemote gitRemoteKind = ParseGitRemoteFromUrl(remoteUrl);
return gitRemoteKind switch
{
@@ -53,7 +53,7 @@ private static GitRemote ParseGitRemoteFromUrl(string repoUrl)
{
{ Host: "github.com" } => GitRemote.GitHub,
{ Host: var host } when host is "dev.azure.com"
- || host.EndsWith("visualstudio.com") => GitRemote.AzureDevOps,
+ || host.EndsWith("visualstudio.com", StringComparison.Ordinal) => GitRemote.AzureDevOps,
_ => GitRemote.None,
};
}
diff --git a/eng/update-dependencies/Git/RemoteGitRepoHelper.cs b/eng/update-dependencies/Git/RemoteGitRepoHelper.cs
index 3491b5f183..8a5d42d16f 100644
--- a/eng/update-dependencies/Git/RemoteGitRepoHelper.cs
+++ b/eng/update-dependencies/Git/RemoteGitRepoHelper.cs
@@ -36,7 +36,7 @@ public Task CreateRemoteBranchAsync(string newBranch, string baseBranch)
///
public async Task CreatePullRequestAsync(PullRequestCreationInfo request)
{
- var darcPullRequest = new PullRequest
+ PullRequest darcPullRequest = new PullRequest
{
Title = request.Title,
Description = request.Body,
@@ -48,8 +48,8 @@ public async Task CreatePullRequestAsync(PullRequestCreationInfo request
"Creating pull request '{PrTitle}' from branch {PrHeadBranch} to {PrBaseBranch}",
darcPullRequest.Title, darcPullRequest.HeadBranch, darcPullRequest.BaseBranch);
- var pullRequestInfo = await _remoteGitRepo.CreatePullRequestAsync(RemoteUrl, darcPullRequest);
- var pullRequestApiUrl = pullRequestInfo.Url;
+ PullRequest pullRequestInfo = await _remoteGitRepo.CreatePullRequestAsync(RemoteUrl, darcPullRequest);
+ string pullRequestApiUrl = pullRequestInfo.Url;
_logger.LogInformation(
"Created pull request {PullRequestTitle} at {PullRequestUrl}",
diff --git a/eng/update-dependencies/GitRemote.cs b/eng/update-dependencies/GitRemote.cs
index eea41cd54e..e592acf5d2 100644
--- a/eng/update-dependencies/GitRemote.cs
+++ b/eng/update-dependencies/GitRemote.cs
@@ -1,5 +1,5 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.DotNet.Docker.UpdateDependencies;
diff --git a/eng/update-dependencies/ManifestHelper.cs b/eng/update-dependencies/ManifestHelper.cs
index 1549294abf..56d841174a 100644
--- a/eng/update-dependencies/ManifestHelper.cs
+++ b/eng/update-dependencies/ManifestHelper.cs
@@ -21,26 +21,21 @@ public static partial class ManifestHelper
///
/// Name of the branch. This is typically "main" or "nightly".
///
+ /// The version source used to identify the product.
+ /// Whether the release updates only the SDK.
public static IEnumerable GetBaseUrlVariableNames(
string dockerfileVersion,
string branch,
string versionSourceName = "",
bool sdkOnlyRelease = false)
{
- string product;
- if (sdkOnlyRelease)
- {
- product = "sdk";
- }
- else
- {
- product = versionSourceName switch
+ string product = sdkOnlyRelease
+ ? "sdk"
+ : versionSourceName switch
{
string v when v.Contains("aspire-dashboard") => "aspire-dashboard",
_ => "dotnet",
};
- }
-
return [
$"{product}|{dockerfileVersion}|base-url|{branch}",
$"{product}|{dockerfileVersion}|base-url|checksums|{branch}",
diff --git a/eng/update-dependencies/ManifestVariables.cs b/eng/update-dependencies/ManifestVariables.cs
index 3517be8315..a2fff828d4 100644
--- a/eng/update-dependencies/ManifestVariables.cs
+++ b/eng/update-dependencies/ManifestVariables.cs
@@ -22,8 +22,7 @@ public sealed partial class ManifestVariables
{
// Token ranges always refer to the original text. Keeping edits separately avoids shifting
// later ranges and lets a reverted value recover its original JSON escaping.
- private readonly string _originalContent;
- private readonly Dictionary _variables = new(StringComparer.Ordinal);
+ private readonly Dictionary _variables = new Dictionary(StringComparer.Ordinal);
///
/// Creates an editable snapshot of a complete manifest.
@@ -39,15 +38,15 @@ public sealed partial class ManifestVariables
///
public ManifestVariables(string content)
{
- _originalContent = content;
+ Content = content;
byte[] utf8 = Encoding.UTF8.GetBytes(content);
- var readerOptions = new JsonReaderOptions
+ JsonReaderOptions readerOptions = new JsonReaderOptions
{
CommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
};
- var reader = new Utf8JsonReader(utf8, readerOptions);
+ Utf8JsonReader reader = new Utf8JsonReader(utf8, readerOptions);
reader.Read();
RequireToken(reader, JsonTokenType.StartObject);
@@ -111,7 +110,7 @@ public string Content
{
get
{
- var content = new StringBuilder();
+ StringBuilder content = new StringBuilder();
int position = 0;
foreach (Variable variable in _variables.Values.OrderBy(variable => variable.OriginalTokenStart))
@@ -121,12 +120,12 @@ public string Content
continue;
}
- content.Append(_originalContent, position, variable.OriginalTokenStart - position);
+ content.Append(field, position, variable.OriginalTokenStart - position);
content.Append(JsonSerializer.Serialize(variable.Value));
position = variable.OriginalTokenStart + variable.OriginalTokenLength;
}
- content.Append(_originalContent, position, _originalContent.Length - position);
+ content.Append(field, position, field.Length - position);
return content.ToString();
}
}
@@ -276,7 +275,7 @@ private void ReadVariables(ref Utf8JsonReader reader, byte[] utf8)
int gapCharacterCount = Encoding.UTF8.GetCharCount(utf8.AsSpan(bytePosition, gapLength));
int originalTokenStart = characterPosition + gapCharacterCount;
int originalTokenLength = Encoding.UTF8.GetCharCount(utf8.AsSpan(byteStart, byteLength));
- var variable = new Variable(value, value, originalTokenStart, originalTokenLength);
+ Variable variable = new Variable(value, value, originalTokenStart, originalTokenLength);
bytePosition = byteStart + byteLength;
characterPosition = originalTokenStart + originalTokenLength;
diff --git a/eng/update-dependencies/Model/Release/ReleaseConfig.cs b/eng/update-dependencies/Model/Release/ReleaseConfig.cs
index 4a6e6ac4c6..bc3ad833d9 100644
--- a/eng/update-dependencies/Model/Release/ReleaseConfig.cs
+++ b/eng/update-dependencies/Model/Release/ReleaseConfig.cs
@@ -52,13 +52,13 @@ public record ReleaseConfig
[MemberNotNullWhen(false, nameof(AspBuild))]
public required bool SdkOnly { get; init; }
- private static readonly JsonSerializerOptions s_jsonOptions = new()
+ private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true
};
public override string ToString() =>
- JsonSerializer.Serialize(this, s_jsonOptions);
+ JsonSerializer.Serialize(this, JsonOptions);
public static ReleaseConfig FromJson(string json) =>
JsonSerializer.Deserialize(json)
diff --git a/eng/update-dependencies/PipelineArtifactProvider.cs b/eng/update-dependencies/PipelineArtifactProvider.cs
index 1a85018d1a..08d36c0f97 100644
--- a/eng/update-dependencies/PipelineArtifactProvider.cs
+++ b/eng/update-dependencies/PipelineArtifactProvider.cs
@@ -50,7 +50,7 @@ internal class PipelineArtifactProvider(
private readonly ILogger _logger = logger;
private readonly AzdoHttpClient _azdoHttpClient = azdoHttpClient;
- private static readonly IEnumerable s_releaseConfigFiles =
+ private static readonly IEnumerable ReleaseConfigFiles =
[
// Newest known location for release artifact data
new PipelineArtifactFile("manifests", "ReleaseConfig.json"),
@@ -67,11 +67,11 @@ public async Task GetReleaseConfigAsync(
string azdoProject,
int stagingPipelineRunId)
{
- var releaseConfigJson = await GetArtifactTextContentAsync(
+ string releaseConfigJson = await GetArtifactTextContentAsync(
azdoOrganization,
azdoProject,
stagingPipelineRunId,
- s_releaseConfigFiles);
+ ReleaseConfigFiles);
return ReleaseConfig.FromJson(releaseConfigJson);
}
@@ -97,6 +97,7 @@ public async Task GetArtifactTextContentAsync(
{
throw new ArgumentException("Azure DevOps organization is required", nameof(azdoOrganization));
}
+
if (string.IsNullOrWhiteSpace(azdoProject))
{
throw new ArgumentException("Azure DevOps project is required", nameof(azdoProject));
@@ -107,7 +108,7 @@ public async Task GetArtifactTextContentAsync(
foreach (PipelineArtifactFile pipelineArtifact in artifactsToTry)
{
_logger.LogInformation(
- "Trying to get artifact from pipeline run {pipelineRunId} using {artifactFile}",
+ "Trying to get artifact from pipeline run {PipelineRunId} using {ArtifactFile}",
pipelineRunId, pipelineArtifact);
try
@@ -126,17 +127,17 @@ public async Task GetArtifactTextContentAsync(
$"/content?format=file&subPath=%2F{pipelineArtifact.SubPath}");
_logger.LogInformation(
- "Downloading artifact {pipelineArtifact} from {downloadUrl}",
+ "Downloading artifact {PipelineArtifact} from {DownloadUrl}",
pipelineArtifact, downloadUrl);
- using var response = await _azdoHttpClient.GetAsync(downloadUrl);
+ using HttpResponseMessage response = await _azdoHttpClient.GetAsync(downloadUrl);
response.EnsureSuccessStatusCode();
- var textContent = await response.Content.ReadAsStringAsync();
+ string textContent = await response.Content.ReadAsStringAsync();
return textContent;
}
catch (Exception e)
{
// Log the exception and continue to the next artifact file
- _logger.LogError(e, "Failed to retrieve {artifactFile}: {message}", pipelineArtifact, e.Message);
+ _logger.LogError(e, "Failed to retrieve {ArtifactFile}: {Message}", pipelineArtifact, e.Message);
exceptions.Add(e);
}
}
diff --git a/eng/update-dependencies/PipelinesService.cs b/eng/update-dependencies/PipelinesService.cs
index 7855262de4..f2a32d57bc 100644
--- a/eng/update-dependencies/PipelinesService.cs
+++ b/eng/update-dependencies/PipelinesService.cs
@@ -3,6 +3,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.TeamFoundation.Build.WebApi;
+using Microsoft.VisualStudio.Services.WebApi;
namespace Microsoft.DotNet.Docker.UpdateDependencies;
@@ -27,20 +28,20 @@ internal sealed class PipelinesService(IAzdoAuthProvider azdoAuthProvider) : IPi
///
public async Task GetArtifactAsync(string azdoOrg, string azdoProject, int buildId, string artifactName)
{
- var client = GetBuildHttpClient(azdoOrg);
+ BuildHttpClient client = GetBuildHttpClient(azdoOrg);
return await client.GetArtifactAsync(azdoProject, buildId, artifactName);
}
///
public async Task> GetBuildTagsAsync(string azdoOrg, string azdoProject, int buildId)
{
- var client = GetBuildHttpClient(azdoOrg);
+ BuildHttpClient client = GetBuildHttpClient(azdoOrg);
return await client.GetBuildTagsAsync(azdoProject, buildId);
}
private BuildHttpClient GetBuildHttpClient(string azdoOrg)
{
- var connection = _azdoAuthProvider.GetVssConnection(azdoOrg);
+ VssConnection connection = _azdoAuthProvider.GetVssConnection(azdoOrg);
return connection.GetClient();
}
}
diff --git a/eng/update-dependencies/Program.cs b/eng/update-dependencies/Program.cs
index 66e248d453..e2aa4aa6b2 100644
--- a/eng/update-dependencies/Program.cs
+++ b/eng/update-dependencies/Program.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.CommandLine;
-using Maestro.Common;
using Maestro.Common.Telemetry;
using Microsoft.DotNet.DarcLib;
using Microsoft.DotNet.DarcLib.Helpers;
@@ -19,7 +18,7 @@
using Microsoft.Extensions.Logging;
using AzureDevOpsClient = Microsoft.DotNet.DarcLib.AzureDevOpsClient;
-var builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings
+HostApplicationBuilder builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings
{
ContentRootPath = AppContext.BaseDirectory,
});
@@ -31,7 +30,7 @@
options.SingleLine = false;
});
-var configuration = builder.Configuration
+UpdateDependenciesConfiguration configuration = builder.Configuration
.GetRequiredSection("UpdateDependencies")
.Get()
?? throw new InvalidOperationException("Failed to bind UpdateDependencies configuration.");
@@ -49,8 +48,8 @@
// Auth services needed for DarcLib remote git operations
services.AddSingleton(sp =>
{
- var azdoTokenProvider = sp.GetRequiredService();
- var gitHubTokenProvider = new ResolvedTokenProvider(configuration.GitHub.Token);
+ IAzureDevOpsTokenProvider azdoTokenProvider = sp.GetRequiredService();
+ ResolvedTokenProvider gitHubTokenProvider = new ResolvedTokenProvider(configuration.GitHub.Token);
return new RemoteTokenProvider(
azdoTokenProvider: azdoTokenProvider,
gitHubTokenProvider: gitHubTokenProvider);
@@ -72,7 +71,7 @@
// Process-based git client
services.AddSingleton();
// LocalGitClient wants a non-generic ILogger, for some reason.
-services.AddSingleton(sp => sp.GetRequiredService().CreateLogger(nameof(LocalGitClient)));
+services.AddSingleton(sp => sp.GetRequiredService().CreateLogger(nameof(LocalGitClient)));
// Git repo cloner that calls out to the `git` executable. It is lighter on memory
// than the LibGit2Sharp-based implementation.
services.AddSingleton();
@@ -98,8 +97,8 @@
services.AddSingleton(_ =>
{
- var productHeader = new Octokit.ProductHeaderValue("dotnet-docker-update-dependencies");
- var client = new Octokit.GitHubClient(productHeader);
+ Octokit.ProductHeaderValue productHeader = new Octokit.ProductHeaderValue("dotnet-docker-update-dependencies");
+ Octokit.GitHubClient client = new Octokit.GitHubClient(productHeader);
if (!string.IsNullOrWhiteSpace(configuration.GitHub.Token))
{
client.Credentials = new Octokit.Credentials(configuration.GitHub.Token);
@@ -127,7 +126,7 @@
using IHost host = builder.Build();
-var rootCommand = new RootCommand("Update dotnet-docker dependencies")
+RootCommand rootCommand = new RootCommand("Update dotnet-docker dependencies")
{
DependencyCommand.CreateCliCommand(host.Services),
DependencyCommand.CreateCliCommand(host.Services),
diff --git a/eng/update-dependencies/ScriptRunner.cs b/eng/update-dependencies/ScriptRunner.cs
index ac70b54f66..c015d6661a 100644
--- a/eng/update-dependencies/ScriptRunner.cs
+++ b/eng/update-dependencies/ScriptRunner.cs
@@ -1,5 +1,5 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using System.Diagnostics;
@@ -28,7 +28,7 @@ private static async Task RunAsync(string scriptPath, string repoRoot, Cancellat
cancellationToken.ThrowIfCancellationRequested();
Trace.TraceInformation($"Executing '{scriptPath}'");
- var startInfo = new ProcessStartInfo("pwsh")
+ ProcessStartInfo startInfo = new ProcessStartInfo("pwsh")
{
WorkingDirectory = repoRoot,
UseShellExecute = false,
@@ -36,7 +36,7 @@ private static async Task RunAsync(string scriptPath, string repoRoot, Cancellat
startInfo.ArgumentList.Add("-NoProfile");
startInfo.ArgumentList.Add("-File");
startInfo.ArgumentList.Add(scriptPath);
- using var process = new Process { StartInfo = startInfo };
+ using Process process = new Process { StartInfo = startInfo };
// Support both execution within Windows 10, Nano Server and Linux environments.
try
diff --git a/eng/update-dependencies/Sync/IInternalVersionsService.cs b/eng/update-dependencies/Sync/IInternalVersionsService.cs
index a4e3109446..2b6dc941a8 100644
--- a/eng/update-dependencies/Sync/IInternalVersionsService.cs
+++ b/eng/update-dependencies/Sync/IInternalVersionsService.cs
@@ -24,6 +24,7 @@ internal interface IInternalVersionsService
/// If a version already exists for the same dockerfileVersion, it will be
/// overwritten.
///
+ /// The root of the repository to update.
/// .NET build or product version
/// the stage container name (e.g., "stage-1234567")
void RecordInternalStagingBuild(string repoRoot, DotNetVersion dotNetVersion, string stageContainer);
diff --git a/eng/update-dependencies/Sync/InternalStageContainers.cs b/eng/update-dependencies/Sync/InternalStageContainers.cs
index bd3a610f9c..cb59661145 100644
--- a/eng/update-dependencies/Sync/InternalStageContainers.cs
+++ b/eng/update-dependencies/Sync/InternalStageContainers.cs
@@ -22,7 +22,7 @@ internal sealed record InternalStageContainers(ImmutableDictionary
public static InternalStageContainers Parse(IEnumerable lines)
{
- var versions = lines
+ ImmutableDictionary versions = lines
.Select(line => line.Split('=', 2))
.Where(parts => parts.Length == 2)
.ToImmutableDictionary(
diff --git a/eng/update-dependencies/Sync/InternalVersionsService.cs b/eng/update-dependencies/Sync/InternalVersionsService.cs
index 139400c514..25d2d57db4 100644
--- a/eng/update-dependencies/Sync/InternalVersionsService.cs
+++ b/eng/update-dependencies/Sync/InternalVersionsService.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections.Immutable;
using Microsoft.DotNet.Docker.Shared;
namespace Microsoft.DotNet.Docker.UpdateDependencies.Sync;
@@ -14,15 +13,15 @@ internal sealed class InternalVersionsService : IInternalVersionsService
///
public InternalStageContainers GetInternalStagingBuilds(string repoRoot)
{
- var internalVersionFile = Path.Combine(repoRoot, InternalVersionsFileName);
+ string internalVersionFile = Path.Combine(repoRoot, InternalVersionsFileName);
try
{
- var fileContents = File.ReadAllLines(internalVersionFile);
+ string[] fileContents = File.ReadAllLines(internalVersionFile);
return InternalStageContainers.Parse(fileContents);
}
catch (FileNotFoundException)
{
- return new InternalStageContainers(ImmutableDictionary.Empty);
+ return new InternalStageContainers([]);
}
}
@@ -39,9 +38,9 @@ public void RecordInternalStagingBuild(string repoRoot, DotNetVersion dotNetVers
// 2) lots of regex JSON manipulation which is error-prone and harder to maintain
//
// So for now, the separate file and format is a compromise.
- var builds = GetInternalStagingBuilds(repoRoot);
+ InternalStageContainers builds = GetInternalStagingBuilds(repoRoot);
builds = builds.Add(dotNetVersion, stageContainerName);
- var internalVersionFile = Path.Combine(repoRoot, InternalVersionsFileName);
+ string internalVersionFile = Path.Combine(repoRoot, InternalVersionsFileName);
File.WriteAllText(internalVersionFile, builds.ToString());
}
}
diff --git a/eng/update-dependencies/Updaters/AspireUpdater.cs b/eng/update-dependencies/Updaters/AspireUpdater.cs
index 3cab0e6142..d952a4f207 100644
--- a/eng/update-dependencies/Updaters/AspireUpdater.cs
+++ b/eng/update-dependencies/Updaters/AspireUpdater.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Globalization;
using Microsoft.DotNet.DarcLib;
using Microsoft.DotNet.ProductConstructionService.Client.Models;
using Microsoft.Extensions.Logging;
@@ -38,7 +39,7 @@ public Task ResolveFromBarBuildAsync(Build build, Cancellation
throw new ArgumentException($"BAR build {build.Id} is not an Aspire build.", nameof(build));
}
- var dashboardAssets = build.Assets.Where(asset =>
+ Asset[] dashboardAssets = build.Assets.Where(asset =>
asset.Name.Contains("aspire-dashboard-linux-x64")
|| asset.Name.Contains("aspire-dashboard-linux-arm64"))
.ToArray();
@@ -65,10 +66,10 @@ private async Task ApplyAsync(
string branch = variables.GetValue("branch");
string dashboardBaseUrl = variables.GetValue($"aspire-dashboard|base-url|{branch}");
- var dashboardChecksums = await Task.WhenAll(dashboardAssets
+ string[] dashboardChecksums = await Task.WhenAll(dashboardAssets
.Select(asset => GetChecksumAsync($"{dashboardBaseUrl}/{asset.Name}", cancellationToken)));
- var majorMinorVersion = VersionHelper.ResolveMajorMinorVersion(version);
+ Version majorMinorVersion = VersionHelper.ResolveMajorMinorVersion(version);
// Aspire Dashboard builds always use "preview 1"; tags use the stable product version.
string productVersion = VersionHelper.ResolveProductVersion(version, isStableRelease: true);
@@ -77,9 +78,9 @@ private async Task ApplyAsync(
variables.SetValue("aspire-dashboard|product-version", productVersion);
variables.SetValue("aspire-dashboard|fixed-tag", productVersion);
variables.SetValue("aspire-dashboard|minor-tag", majorMinorVersion.ToString(2));
- variables.SetValue("aspire-dashboard|major-tag", majorMinorVersion.Major.ToString());
+ variables.SetValue("aspire-dashboard|major-tag", majorMinorVersion.Major.ToString(CultureInfo.InvariantCulture));
- foreach (var (asset, checksum) in dashboardAssets.Zip(dashboardChecksums))
+ foreach ((Asset? asset, string? checksum) in dashboardAssets.Zip(dashboardChecksums))
{
string arch = asset.Name.Contains("arm64") ? "arm64" : "x64";
variables.SetValue($"aspire-dashboard|linux|{arch}|sha", checksum);
diff --git a/eng/update-dependencies/Updaters/BaseUrlUpdater.cs b/eng/update-dependencies/Updaters/BaseUrlUpdater.cs
index 833396373e..27f6679cae 100644
--- a/eng/update-dependencies/Updaters/BaseUrlUpdater.cs
+++ b/eng/update-dependencies/Updaters/BaseUrlUpdater.cs
@@ -20,8 +20,8 @@ public static void Update(
string internalBaseUrl,
bool sdkOnlyRelease)
{
- var upstreamBranch = manifestVariables.GetValue("branch");
- var baseUrlVarNames = ManifestHelper.GetBaseUrlVariableNames(
+ string upstreamBranch = manifestVariables.GetValue("branch");
+ IEnumerable baseUrlVarNames = ManifestHelper.GetBaseUrlVariableNames(
dockerfileVersion: dockerfileVersion,
branch: upstreamBranch,
sdkOnlyRelease: sdkOnlyRelease);
diff --git a/eng/update-dependencies/Updaters/ChiselUpdater.cs b/eng/update-dependencies/Updaters/ChiselUpdater.cs
index 655a37db0a..f3c8f7ee92 100644
--- a/eng/update-dependencies/Updaters/ChiselUpdater.cs
+++ b/eng/update-dependencies/Updaters/ChiselUpdater.cs
@@ -1,5 +1,5 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.RegularExpressions;
using Octokit;
@@ -15,12 +15,10 @@ public sealed class ChiselUpdater(IReleasesClient releases, HttpClient httpClien
public static string Name => "chisel";
public static string VersionSourceName => "chisel";
- private static readonly string[] s_supportedArchitectures = ["amd64", "arm", "arm64"];
- private static string GetChiselManifestVariable(string product, string arch, string type)
- {
+ private static readonly string[] SupportedArchitectures = ["amd64", "arm", "arm64"];
+ private static string GetChiselManifestVariable(string product, string arch, string type) =>
// Workaround for ambiguous method call, will be fixed with https://github.com/dotnet/csharplang/issues/8374
- return string.Join('|', new string[] { product, "latest", ToManifestArch(arch), type });
- }
+ string.Join('|', new string[] { product, "latest", ToManifestArch(arch), type });
private static Regex GetAssetRegex(string arch) => new(@"chisel_v\d+\.\d+\.\d+_linux_" + arch + @"\.tar\.gz");
@@ -53,7 +51,7 @@ private async Task ApplyAsync(
variables.SetValue(BuildVersionVariable, release.TagName);
- foreach (string arch in s_supportedArchitectures)
+ foreach (string arch in SupportedArchitectures)
{
string urlVariable = GetChiselManifestVariable(Name, arch, "url");
string shaVariable = GetChiselManifestVariable(Name, arch, "sha384");
diff --git a/eng/update-dependencies/Updaters/DependencyUpdate.cs b/eng/update-dependencies/Updaters/DependencyUpdate.cs
index 23b3e3ffad..103a9199f5 100644
--- a/eng/update-dependencies/Updaters/DependencyUpdate.cs
+++ b/eng/update-dependencies/Updaters/DependencyUpdate.cs
@@ -14,6 +14,7 @@ namespace Microsoft.DotNet.Docker.UpdateDependencies.Updaters;
/// The absolute path of the repo to update. This is not always the repo the command was run
/// from: when creating a pull request, the update is applied to a temporary clone instead.
///
+/// The token used to cancel the update.
public delegate Task ApplyUpdateAsync(
ManifestVariables variables,
string repoRoot,
diff --git a/eng/update-dependencies/Updaters/DotNetUpdater.cs b/eng/update-dependencies/Updaters/DotNetUpdater.cs
index 5d85da0e86..f7836d2035 100644
--- a/eng/update-dependencies/Updaters/DotNetUpdater.cs
+++ b/eng/update-dependencies/Updaters/DotNetUpdater.cs
@@ -1,8 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Microsoft.DotNet.Docker.UpdateDependencies.Model.Release;
using Microsoft.DotNet.DarcLib;
+using Microsoft.DotNet.Docker.UpdateDependencies.Model.Release;
using Microsoft.DotNet.ProductConstructionService.Client.Models;
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
@@ -24,9 +24,9 @@ public async Task ResolveFromBarBuildAsync(Build build, Cancel
logger.LogInformation("Updating .NET from BAR build {BuildId} at {Commit}", build.Id, build.Commit);
IEnumerable assets = await barClient.GetAssetsAsync(buildId: build.Id).WaitAsync(cancellationToken);
- var versions = ProductVersions.FromVmrBuildAssets(assets);
+ ProductVersions versions = ProductVersions.FromVmrBuildAssets(assets);
string dockerfileVersion = VersionHelper.ResolveMajorMinorVersion(versions.Sdk.Version).ToString(2);
- var productVersions = new Dictionary
+ Dictionary productVersions = new Dictionary
{
["dotnet"] = versions.Runtime.Version,
["runtime"] = versions.Runtime.Version,
@@ -61,7 +61,7 @@ public Task UpdateFromStagingPipelineAsync(
{
bool isInternal = !string.IsNullOrEmpty(internalBaseUrl);
string dockerfileVersion = VersionHelper.ResolveMajorMinorVersion(release.RuntimeBuild).ToString(2);
- var productVersions = new Dictionary();
+ Dictionary productVersions = [];
// SDK-only releases are almost always one-off updates/bug
// fixes on top of an existing release of the Runtime and
@@ -78,6 +78,7 @@ public Task UpdateFromStagingPipelineAsync(
productVersions["aspnet"] = isInternal ? release.AspBuild : release.Asp;
productVersions["aspnet-composite"] = productVersions["aspnet"];
}
+
productVersions["sdk"] = VersionHelper.GetHighestSdkVersion(isInternal ? release.SdkBuilds : release.Sdks);
logger.LogInformation("Resolved .NET product versions: {Versions}",
@@ -90,18 +91,18 @@ private void ApplyProductVersions(
ManifestVariables variables,
string repoRoot,
string dockerfileVersion,
- IReadOnlyDictionary productVersions,
+ Dictionary productVersions,
string internalBaseUrl)
{
NuGetConfigUpdater.Update(variables, repoRoot, dockerfileVersion, productVersions["sdk"], !string.IsNullOrEmpty(internalBaseUrl));
BaseUrlUpdater.Update(variables, dockerfileVersion, internalBaseUrl, productVersions.Count == 1);
- foreach (var (product, version) in productVersions)
+ foreach ((string? product, string? version) in productVersions)
{
string productVersion = "";
if (version is not null)
{
- var parsedVersion = SemanticVersion.Parse(version);
+ SemanticVersion parsedVersion = SemanticVersion.Parse(version);
productVersion = $"{parsedVersion.Major}.{parsedVersion.Minor}.{parsedVersion.Patch}";
if (parsedVersion.IsPrerelease && parsedVersion.ReleaseLabels.First() is not ("servicing" or "rtm"))
{
diff --git a/eng/update-dependencies/Updaters/MonitorUpdater.cs b/eng/update-dependencies/Updaters/MonitorUpdater.cs
index bb447c4959..034102103b 100644
--- a/eng/update-dependencies/Updaters/MonitorUpdater.cs
+++ b/eng/update-dependencies/Updaters/MonitorUpdater.cs
@@ -13,7 +13,7 @@ public sealed class MonitorUpdater(
UpdateDependenciesConfiguration configuration,
ILogger logger)
{
- private static readonly (string Product, string ArchiveName)[] s_products =
+ private static readonly (string Product, string ArchiveName)[] Products =
[
("monitor", "dotnet-monitor"),
("monitor-base", "dotnet-monitor-base"),
@@ -31,7 +31,7 @@ public async Task ResolveFromPipelineBuildAsync(
private async Task GetVersionFromPipelineAsync(int pipelineRunId, CancellationToken cancellationToken)
{
- var versionFile = new PipelineArtifactFile("Build_Info", "dotnet-monitor.nupkg.buildversion");
+ PipelineArtifactFile versionFile = new PipelineArtifactFile("Build_Info", "dotnet-monitor.nupkg.buildversion");
string version = await pipelineArtifactProvider
.GetArtifactTextContentAsync(
configuration.AzureDevOps.Organization,
@@ -46,7 +46,7 @@ private async Task GetVersionFromPipelineAsync(int pipelineRunId, Cancel
public DependencyUpdate ResolveFromVersion(string version)
{
version = version.Trim();
- var parsedVersion = SemanticVersion.Parse(version);
+ SemanticVersion parsedVersion = SemanticVersion.Parse(version);
string dockerfileVersion = $"{parsedVersion.Major}.{parsedVersion.Minor}";
return new DependencyUpdate(
@@ -80,16 +80,16 @@ private async Task ApplyAsync(
variables.SetValue(baseUrlVariable, $"$(base-url|public|{quality}|{branch})");
variables.SetValue(checksumsBaseUrlVariable, $"$(base-url|public-checksums|{quality}|{branch})");
- foreach (var (product, _) in s_products)
+ foreach ((string? product, string _) in Products)
{
UpdateVersion(variables, $"{product}|{dockerfileVersion}|build-version", version);
UpdateVersion(variables, $"{product}|{dockerfileVersion}|product-version", productVersion);
}
- foreach (var (product, archiveName) in s_products)
+ foreach ((string? product, string? archiveName) in Products)
{
string prefix = $"{product}|{dockerfileVersion}|";
- var checksumVariables = variables.Names
+ IEnumerable checksumVariables = variables.Names
.Where(name => name.StartsWith(prefix, StringComparison.Ordinal))
.Where(name => name.EndsWith("|sha", StringComparison.Ordinal));
diff --git a/eng/update-dependencies/Updaters/NuGetConfigUpdater.cs b/eng/update-dependencies/Updaters/NuGetConfigUpdater.cs
index e2eff00a17..ae5757fbde 100644
--- a/eng/update-dependencies/Updaters/NuGetConfigUpdater.cs
+++ b/eng/update-dependencies/Updaters/NuGetConfigUpdater.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Globalization;
using System.Text;
using System.Xml.Linq;
using Microsoft.DotNet.Docker.Shared;
@@ -60,6 +61,7 @@ private static string ToStringWithDeclaration(XDocument doc)
// Using the Save method preserves the XML declaration header. ToString doesn't do that.
doc.Save(writer);
}
+
return builder.ToString();
}
@@ -129,7 +131,7 @@ private static void UpdatePackageSources(
private static void RemoveAllInternalPackageSources(XElement packageSources)
{
IEnumerable elements = packageSources.Elements()
- .Where(pkgSrc => pkgSrc.Attribute("key")?.Value.EndsWith(PkgSrcSuffix) == true)
+ .Where(pkgSrc => pkgSrc.Attribute("key")?.Value.EndsWith(PkgSrcSuffix, StringComparison.Ordinal) == true)
.ToList();
foreach (XElement element in elements)
{
@@ -176,13 +178,8 @@ private static T GetOrCreateXObject(T? node, XContainer parent, Func creat
return node;
}
- private class Utf8StringWriter : StringWriter
+ private class Utf8StringWriter(StringBuilder stringBuilder) : StringWriter(stringBuilder, CultureInfo.InvariantCulture)
{
- public Utf8StringWriter(StringBuilder stringBuilder)
- : base(stringBuilder)
- {
- }
-
public override Encoding Encoding => Encoding.UTF8;
}
}
diff --git a/eng/update-dependencies/Updaters/PowerShellUpdater.cs b/eng/update-dependencies/Updaters/PowerShellUpdater.cs
index 14658a4a1b..8f5adb5d1c 100644
--- a/eng/update-dependencies/Updaters/PowerShellUpdater.cs
+++ b/eng/update-dependencies/Updaters/PowerShellUpdater.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Globalization;
using System.Net;
using Azure;
using Azure.Core;
@@ -160,7 +161,7 @@ private async Task ExistsAsync(
return await CreateBlobClient(checksumsUrl).ExistsAsync(cancellationToken);
}
- using var request = new HttpRequestMessage(HttpMethod.Head, checksumsUrl);
+ using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Head, checksumsUrl);
using HttpResponseMessage response = await httpClient.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.NotFound)
@@ -176,7 +177,7 @@ private async Task ExistsAsync(
private BlobClient CreateBlobClient(string url)
{
TokenCredential credential = credentialProvider.GetCredential(ServiceConnectionNames.Staging);
- var options = new BlobClientOptions { Transport = new HttpClientTransport(httpClient) };
+ BlobClientOptions options = new BlobClientOptions { Transport = new HttpClientTransport(httpClient) };
return new BlobClient(new Uri(url), credential, options);
}
@@ -232,12 +233,12 @@ private static string GetBaseUrlValue(string dotnetVersion, string version, bool
// Parses lines of the form " *", where '*' marks binary mode and is optional.
private static Dictionary ParseChecksums(string content)
{
- const StringSplitOptions SplitOptions = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries;
- var checksums = new Dictionary();
+ const StringSplitOptions splitOptions = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries;
+ Dictionary checksums = [];
- foreach (string line in content.Split('\n', SplitOptions))
+ foreach (string line in content.Split('\n', splitOptions))
{
- if (line.Split(' ', 2, SplitOptions) is not [var hash, var file])
+ if (line.Split(' ', 2, splitOptions) is not [var hash, var file])
{
throw new FormatException($"Unexpected line in {ChecksumsFileName}: '{line}'");
}
@@ -294,9 +295,9 @@ private static SemanticVersion[] GetNextVersions(SemanticVersion version)
// 7.6.0-preview.5 -> 7.6.0-preview.6, 7.6.0-rc.1, 7.6.0
// 7.6.0-rc.2 -> 7.6.0-rc.3, 7.6.0
- var stableRelease = new SemanticVersion(version.Major, version.Minor, version.Patch);
+ SemanticVersion stableRelease = new SemanticVersion(version.Major, version.Minor, version.Patch);
- var getPrerelease = (string label, int number) =>
+ SemanticVersion GetPrerelease(string label, int number) =>
new SemanticVersion(version.Major, version.Minor, version.Patch, $"{label}.{number}");
return version.ReleaseLabels.ToArray() switch
@@ -304,13 +305,13 @@ private static SemanticVersion[] GetNextVersions(SemanticVersion version)
[] => [new SemanticVersion(version.Major, version.Minor, version.Patch + 1)],
["preview", var number] =>
[
- getPrerelease("preview", int.Parse(number) + 1),
- getPrerelease("rc", 1),
+ GetPrerelease("preview", int.Parse(number, CultureInfo.InvariantCulture) + 1),
+ GetPrerelease("rc", 1),
stableRelease
],
["rc", var number] =>
[
- getPrerelease("rc", int.Parse(number) + 1),
+ GetPrerelease("rc", int.Parse(number, CultureInfo.InvariantCulture) + 1),
stableRelease
],
_ => throw new FormatException($"Unexpected PowerShell version '{version}'."),
diff --git a/eng/update-dependencies/Updaters/RocksToolboxUpdater.cs b/eng/update-dependencies/Updaters/RocksToolboxUpdater.cs
index 558fa99836..da7c8614e7 100644
--- a/eng/update-dependencies/Updaters/RocksToolboxUpdater.cs
+++ b/eng/update-dependencies/Updaters/RocksToolboxUpdater.cs
@@ -1,5 +1,5 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
using Octokit;
diff --git a/eng/update-dependencies/VariableHelper.cs b/eng/update-dependencies/VariableHelper.cs
index ac144d4f2b..7b1bceb6f5 100644
--- a/eng/update-dependencies/VariableHelper.cs
+++ b/eng/update-dependencies/VariableHelper.cs
@@ -1,6 +1,7 @@
-// Copyright (c) .NET Foundation and contributors. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+using System.Globalization;
using System.Text.RegularExpressions;
namespace Microsoft.DotNet.Docker.UpdateDependencies;
@@ -19,8 +20,8 @@ public static Regex GetValueRegex(params string[] variableNames)
{
string variableGroupFormat = $"(?<{VariableGroupName}>({{0}}))";
string joinedVariableNames = string.Join('|', variableNames);
- string upperCasedVariableGroup = string.Format(variableGroupFormat, joinedVariableNames.ToUpperInvariant());
- string lowerCasedVariableGroup = string.Format(variableGroupFormat, joinedVariableNames.ToLowerInvariant());
+ string upperCasedVariableGroup = string.Format(CultureInfo.InvariantCulture, variableGroupFormat, joinedVariableNames.ToUpperInvariant());
+ string lowerCasedVariableGroup = string.Format(CultureInfo.InvariantCulture, variableGroupFormat, joinedVariableNames.ToLowerInvariant());
string argEnvPattern = $" {upperCasedVariableGroup}[= ](?<{ValueGroupName}>[^\\s$]+)";
string linuxPattern = $"\\b{lowerCasedVariableGroup}=(?<{ValueGroupName}>[^\\s]+)";
diff --git a/eng/update-dependencies/VersionHelper.cs b/eng/update-dependencies/VersionHelper.cs
index 7e596be363..6c20a92088 100644
--- a/eng/update-dependencies/VersionHelper.cs
+++ b/eng/update-dependencies/VersionHelper.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Globalization;
+
namespace Microsoft.DotNet.Docker.UpdateDependencies;
internal static class VersionHelper
@@ -13,7 +15,7 @@ public static Version ResolveMajorMinorVersion(string versionString)
throw new InvalidOperationException($"Could not parse major-minor version from '{versionString}'.");
}
- return new Version(major: int.Parse(versionParts[0]), minor: int.Parse(versionParts[1]));
+ return new Version(major: int.Parse(versionParts[0], CultureInfo.InvariantCulture), minor: int.Parse(versionParts[1], CultureInfo.InvariantCulture));
}
///
@@ -23,7 +25,7 @@ public static Version ResolveMajorMinorVersion(string versionString)
/// A version that may already be a simple 3-part version (e.g. "8.0.100"),
/// or a full build version (e.g. "8.0.100-servicing.12345.6").
///
- ///
/// Whether the build version is a stable release. This should be false
/// for preview releases only. Release candidates are considered stable.
///
@@ -38,7 +40,7 @@ public static string ResolveProductVersion(string buildVersion, bool isStableRel
int monikerSeparatorIndex = buildVersion.IndexOf('-');
if (monikerSeparatorIndex >= 0)
{
- return buildVersion.Substring(0, monikerSeparatorIndex);
+ return buildVersion[..monikerSeparatorIndex];
}
}
diff --git a/eng/update-dependencies/update-dependencies.csproj b/eng/update-dependencies/update-dependencies.csproj
index f02a06cf5d..6e9dddf0b6 100644
--- a/eng/update-dependencies/update-dependencies.csproj
+++ b/eng/update-dependencies/update-dependencies.csproj
@@ -3,7 +3,7 @@
Exe
net10.0
- latest
+ 14
enable
enable
Microsoft.DotNet.Docker.UpdateDependencies
diff --git a/tests/Microsoft.DotNet.Docker.Tests/Arch.cs b/tests/Microsoft.DotNet.Docker.Tests/Arch.cs
index cbfc0c1ad0..c96cb53c40 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/Arch.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/Arch.cs
@@ -1,13 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-namespace Microsoft.DotNet.Docker.Tests
+namespace Microsoft.DotNet.Docker.Tests;
+
+public enum Arch
{
- public enum Arch
- {
- Amd64,
- Arm,
- Arm64,
- }
+ Amd64,
+ Arm,
+ Arm64,
}
diff --git a/tests/Microsoft.DotNet.Docker.Tests/AspireDashboardImageTests.cs b/tests/Microsoft.DotNet.Docker.Tests/AspireDashboardImageTests.cs
index efbf8a522c..5912d6875e 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/AspireDashboardImageTests.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/AspireDashboardImageTests.cs
@@ -1,11 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Microsoft.DotNet.Docker.Tests.TestScenarios;
using Xunit;
using Xunit.Abstractions;
@@ -55,7 +54,7 @@ public void VerifyEnvironmentVariables(ProductImageData imageData)
];
string imageTag = imageData.GetImage(ImageRepo, DockerHelper);
- EnvironmentVariableInfo.Validate(expectedVariables, imageTag, imageData, DockerHelper);
+ EnvironmentVariableInfo.Validate(expectedVariables, imageTag, DockerHelper);
}
[LinuxImageTheory]
@@ -67,7 +66,7 @@ public void VerifyInstalledPackages(ProductImageData imageData)
IEnumerable expectedPackages =
GetExpectedPackages(imageData with { ImageVariant = DotNetImageVariant.Extra }, ImageRepo);
IEnumerable actualPackages =
- GetInstalledPackages(imageData, ImageRepo, [ AppPath ]);
+ GetInstalledPackages(imageData, ImageRepo, [AppPath]);
string imageName = imageData.GetImage(ImageRepo, DockerHelper, skipPull: true);
ComparePackages(expectedPackages, actualPackages, imageData.IsDistroless, imageName, OutputHelper);
diff --git a/tests/Microsoft.DotNet.Docker.Tests/AspireDashboardManifestTests.cs b/tests/Microsoft.DotNet.Docker.Tests/AspireDashboardManifestTests.cs
index da5583ee91..a8f8acebf3 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/AspireDashboardManifestTests.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/AspireDashboardManifestTests.cs
@@ -1,6 +1,5 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System.Linq;
using Newtonsoft.Json.Linq;
@@ -17,10 +16,7 @@ public sealed class AspireDashboardManifestTests
[Theory]
[InlineData("", "aspire/dashboard")]
[InlineData("/nightly", "aspire/nightly/dashboard")]
- public void RepoName_UsesAspireProductFamily(string repoNameModifier, string expectedRepoName)
- {
- Assert.Equal(expectedRepoName, ImageData.GetRepoName(AspireDashboardId, repoNameModifier));
- }
+ public void RepoName_UsesAspireProductFamily(string repoNameModifier, string expectedRepoName) => Assert.Equal(expectedRepoName, ImageData.GetRepoName(AspireDashboardId, repoNameModifier));
[Fact]
public void Manifest_UsesCanonicalRepoName()
diff --git a/tests/Microsoft.DotNet.Docker.Tests/AspnetImageTests.cs b/tests/Microsoft.DotNet.Docker.Tests/AspnetImageTests.cs
index 6815e2f297..f1791c98a2 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/AspnetImageTests.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/AspnetImageTests.cs
@@ -1,131 +1,109 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
+using Microsoft.DotNet.Docker.Tests.TestScenarios;
using Xunit;
using Xunit.Abstractions;
-namespace Microsoft.DotNet.Docker.Tests
+namespace Microsoft.DotNet.Docker.Tests;
+
+[Trait("Category", "aspnet")]
+public class AspnetImageTests(ITestOutputHelper outputHelper) : CommonRuntimeImageTests(outputHelper)
{
- [Trait("Category", "aspnet")]
- public class AspnetImageTests : CommonRuntimeImageTests
+ protected override DotNetImageRepo ImageRepo => DotNetImageRepo.Aspnet;
+
+ public static IEnumerable
- public bool StartsWith(string value) => TagName.StartsWith(value);
+ public bool StartsWith(string value) => TagName.StartsWith(value, StringComparison.Ordinal);
///
/// Implicit conversion to string for backward compatibility with existing code.
diff --git a/tests/Microsoft.DotNet.Docker.Tests/PowerShellTests.cs b/tests/Microsoft.DotNet.Docker.Tests/PowerShellTests.cs
index f4bcdc187d..9d3fd075ec 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/PowerShellTests.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/PowerShellTests.cs
@@ -1,20 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
using System.Linq;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Reflection;
-using System.Security.Cryptography;
-using System.Text;
-using System.Threading.Tasks;
-using Polly;
-using Polly.Retry;
using Xunit;
using Xunit.Abstractions;
@@ -22,13 +11,8 @@
namespace Microsoft.DotNet.Docker.Tests;
[Trait("Category", "sdk")]
-public class PowerShellTests : ProductImageTests
+public class PowerShellTests(ITestOutputHelper outputHelper) : ProductImageTests(outputHelper)
{
- public PowerShellTests(ITestOutputHelper outputHelper)
- : base(outputHelper)
- {
- }
-
protected override DotNetImageRepo ImageRepo => DotNetImageRepo.SDK;
public static IEnumerable GetImageData()
diff --git a/tests/Microsoft.DotNet.Docker.Tests/ProductImageData.cs b/tests/Microsoft.DotNet.Docker.Tests/ProductImageData.cs
index 66f574e5fb..9492d7cba1 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/ProductImageData.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/ProductImageData.cs
@@ -1,221 +1,217 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
-namespace Microsoft.DotNet.Docker.Tests
+namespace Microsoft.DotNet.Docker.Tests;
+
+public record ProductImageData : ImageData
{
- public record ProductImageData : ImageData
- {
- private OSInfo _sdkOS;
- private string _osTag;
- private string _osDir;
- private ImageVersion? _versionFamily;
+ private OSInfo _sdkOS;
+ private ImageVersion? _versionFamily;
- public bool HasCustomSdk => _sdkOS is not null;
+ public bool HasCustomSdk => _sdkOS is not null;
- public bool GlobalizationInvariantMode => !SupportsGlobalization;
+ public bool GlobalizationInvariantMode => !SupportsGlobalization;
- // PowerShell does not support Arm-based Alpine
- public bool SupportsPowerShell => !(OS.Family == OSFamily.Alpine && IsArm);
+ // PowerShell does not support Arm-based Alpine
+ public bool SupportsPowerShell => !(OS.Family == OSFamily.Alpine && IsArm);
- ///
- /// Indicates whether the SDK version of the image supports `dnx` and
- /// should have it installed.
- ///
- public bool SupportsDnx => VersionFamily != ImageVersion.V8_0 && VersionFamily != ImageVersion.V9_0;
+ ///
+ /// Indicates whether the SDK version of the image supports `dnx` and
+ /// should have it installed.
+ ///
+ public bool SupportsDnx => VersionFamily != ImageVersion.V8_0 && VersionFamily != ImageVersion.V9_0;
- public OSInfo SdkOS
- {
- get => HasCustomSdk ? _sdkOS : OS;
- init => _sdkOS = value;
- }
+ public OSInfo SdkOS
+ {
+ get => HasCustomSdk ? _sdkOS : OS;
+ init => _sdkOS = value;
+ }
- public DotNetImageVariant SdkImageVariant { get; init; } = DotNetImageVariant.None;
+ public DotNetImageVariant SdkImageVariant { get; init; } = DotNetImageVariant.None;
- public string OSTag
- {
- get => _osTag ?? OS;
- init => _osTag = value;
- }
+ public string OSTag
+ {
+ get => field ?? OS;
+ init;
+ }
- public string OSDir
- {
- get => _osDir ?? OSTag;
- init => _osDir = value;
- }
+ public string OSDir
+ {
+ get => field ?? OSTag;
+ init;
+ }
- public ImageVersion Version { get; init; }
+ public ImageVersion Version { get; init; }
- public ImageVersion VersionFamily
- {
- get { return _versionFamily.GetValueOrDefault(Version); }
- init { _versionFamily = value; }
- }
+ public ImageVersion VersionFamily
+ {
+ get => _versionFamily.GetValueOrDefault(Version); init => _versionFamily = value;
+ }
- public DotNetImageVariant ImageVariant { get; init;}
+ public DotNetImageVariant ImageVariant { get; init; }
- public DotNetImageRepo SupportedImageRepos { get; init; } = DotNetImageRepo.Runtime_Deps
- | DotNetImageRepo.Runtime
- | DotNetImageRepo.Aspnet
- | DotNetImageRepo.SDK;
+ public DotNetImageRepo SupportedImageRepos { get; init; } = DotNetImageRepo.Runtime_Deps
+ | DotNetImageRepo.Runtime
+ | DotNetImageRepo.Aspnet
+ | DotNetImageRepo.SDK;
- public string VersionString => Version.ToString();
+ public string VersionString => Version.ToString();
- private bool SupportsGlobalization
+ private bool SupportsGlobalization
+ {
+ get
{
- get
- {
- bool isSizeFocusedImage = IsDistroless || OS.Family == OSFamily.Alpine;
- return ImageVariant.HasFlag(DotNetImageVariant.Extra) || !isSizeFocusedImage;
- }
+ bool isSizeFocusedImage = IsDistroless || OS.Family == OSFamily.Alpine;
+ return ImageVariant.HasFlag(DotNetImageVariant.Extra) || !isSizeFocusedImage;
}
+ }
- public string GetDockerfilePath(DotNetImageRepo imageRepo)
+ public string GetDockerfilePath(DotNetImageRepo imageRepo)
+ {
+ // Aspire Dashboard Dockerfiles are not organized by product version or OS.
+ if (imageRepo == DotNetImageRepo.Aspire_Dashboard)
{
- // Aspire Dashboard Dockerfiles are not organized by product version or OS.
- if (imageRepo == DotNetImageRepo.Aspire_Dashboard)
- {
- return $"{GetRepoSrcPath(imageRepo)}/{GetArchLabel()}";
- }
+ return $"{GetRepoSrcPath(imageRepo)}/{GetArchLabel()}";
+ }
- IEnumerable pathComponents =
- [
- GetRepoSrcPath(imageRepo),
- Version.ToString(),
- OSDir + GetVariantSuffix(),
- GetArchLabel()
- ];
+ IEnumerable pathComponents =
+ [
+ GetRepoSrcPath(imageRepo),
+ Version.ToString(),
+ OSDir + GetVariantSuffix(),
+ GetArchLabel()
+ ];
- // Don't use Path.Join since it will use Windows path separators when run locally.
- return string.Join('/', pathComponents);
- }
+ // Don't use Path.Join since it will use Windows path separators when run locally.
+ return string.Join('/', pathComponents);
+ }
- private string GetVariantSuffix() =>
- ImageVariant == DotNetImageVariant.None ? "" : $"-{GetImageVariantName(ImageVariant)}";
+ private string GetVariantSuffix() =>
+ ImageVariant == DotNetImageVariant.None ? "" : $"-{GetImageVariantName(ImageVariant)}";
- public override string GetIdentifier(string type) => $"{VersionString}-{base.GetIdentifier(type)}";
+ public override string GetIdentifier(string type) => $"{VersionString}-{base.GetIdentifier(type)}";
- public static string GetRepoName(DotNetImageRepo imageRepo) => imageRepo switch
- {
- DotNetImageRepo.Monitor_Base => "monitor/base",
- _ => Enum.GetName(imageRepo).ToLowerInvariant().Replace('_', '-')
- };
+ public static string GetRepoName(DotNetImageRepo imageRepo) => imageRepo switch
+ {
+ DotNetImageRepo.Monitor_Base => "monitor/base",
+ _ => Enum.GetName(imageRepo).ToLowerInvariant().Replace('_', '-')
+ };
- private static string GetRepoSrcPath(DotNetImageRepo imageRepo) => "src/" + imageRepo switch
- {
- DotNetImageRepo.Monitor_Base => "monitor-base",
- _ => GetRepoName(imageRepo)
- };
+ private static string GetRepoSrcPath(DotNetImageRepo imageRepo) => "src/" + imageRepo switch
+ {
+ DotNetImageRepo.Monitor_Base => "monitor-base",
+ _ => GetRepoName(imageRepo)
+ };
- public static string GetImageVariantName(DotNetImageVariant imageVariant)
+ public static string GetImageVariantName(DotNetImageVariant imageVariant)
+ {
+ IEnumerable imageVariants = [];
+ foreach (DotNetImageVariant enumValue in Enum.GetValues())
{
- IEnumerable imageVariants = [];
- foreach (DotNetImageVariant enumValue in Enum.GetValues(typeof(DotNetImageVariant)))
+ if (enumValue != DotNetImageVariant.None && imageVariant.HasFlag(enumValue))
{
- if (enumValue != DotNetImageVariant.None && imageVariant.HasFlag(enumValue))
- {
- imageVariants = imageVariants.Append(Enum.GetName(typeof(DotNetImageVariant), enumValue).ToLowerInvariant());
- }
+ imageVariants = imageVariants.Append(Enum.GetName(enumValue).ToLowerInvariant());
}
- return string.Join('-', imageVariants);
}
- public string GetImage(DotNetImageRepo imageRepo, DockerHelper dockerHelper, bool skipPull = false)
- {
- // ASP.NET composite includes its own runtime that we want to test.
- if (ImageVariant.HasFlag(DotNetImageVariant.Composite) && imageRepo == DotNetImageRepo.Runtime)
- {
- imageRepo = DotNetImageRepo.Aspnet;
- }
-
- if (imageRepo != DotNetImageRepo.SDK && !SupportedImageRepos.HasFlag(imageRepo))
- {
- throw new ArgumentOutOfRangeException(nameof(imageRepo),
- $"Unsupported image type '{imageRepo}' for Image Variant '{ImageVariant}'");
- }
-
- string tag = GetTagName(imageRepo);
- string imageName = GetImageName(tag, GetRepoName(imageRepo));
-
- if (!skipPull)
- {
- // Pull the image to ensure it exists
- PullImageIfNecessary(imageName, dockerHelper);
- }
+ return string.Join('-', imageVariants);
+ }
- return imageName;
+ public string GetImage(DotNetImageRepo imageRepo, DockerHelper dockerHelper, bool skipPull = false)
+ {
+ // ASP.NET composite includes its own runtime that we want to test.
+ if (ImageVariant.HasFlag(DotNetImageVariant.Composite) && imageRepo == DotNetImageRepo.Runtime)
+ {
+ imageRepo = DotNetImageRepo.Aspnet;
}
- public string GetProductVersion(DotNetImageRepo imageRepoToUse, DotNetImageRepo productVersionRepo, DockerHelper dockerHelper)
+ if (imageRepo != DotNetImageRepo.SDK && !SupportedImageRepos.HasFlag(imageRepo))
{
- string imageName = GetImage(imageRepoToUse, dockerHelper);
- return GetProductVersion(imageName, productVersionRepo, dockerHelper);
+ throw new ArgumentOutOfRangeException(nameof(imageRepo),
+ $"Unsupported image type '{imageRepo}' for Image Variant '{ImageVariant}'");
}
- public string GetProductVersion(string imageName, DotNetImageRepo productVersionRepo, DockerHelper dockerHelper)
- {
- string containerName = GetIdentifier($"GetProductVersion-{productVersionRepo}");
+ string tag = GetTagName(imageRepo);
+ string imageName = GetImageName(tag, GetRepoName(imageRepo));
- return productVersionRepo switch
- {
- DotNetImageRepo.SDK => dockerHelper.Run(imageName, containerName, "dotnet --version"),
- DotNetImageRepo.Runtime => GetRuntimeVersion(imageName, containerName, "Microsoft.NETCore.App", dockerHelper),
- DotNetImageRepo.Aspnet => GetRuntimeVersion(imageName, containerName, "Microsoft.AspNetCore.App", dockerHelper),
- _ => throw new NotSupportedException($"Unsupported image type '{productVersionRepo}'"),
- };
+ if (!skipPull)
+ {
+ // Pull the image to ensure it exists
+ PullImageIfNecessary(imageName, dockerHelper);
}
- private string GetRuntimeVersion(string imageName, string containerName, string runtimeName, DockerHelper dockerHelper)
- {
- const string versionGroupName = "Version";
+ return imageName;
+ }
- string runtimeListing = dockerHelper.Run(imageName, containerName, FormatDotnetCommand("--list-runtimes"));
- Regex versionRegex = new Regex($"{runtimeName} (?<{versionGroupName}>[^\\s]+) ");
- Match match = versionRegex.Match(runtimeListing);
- return match.Success ? match.Groups[versionGroupName].Value : string.Empty;
- }
+ public string GetProductVersion(DotNetImageRepo imageRepoToUse, DotNetImageRepo productVersionRepo, DockerHelper dockerHelper)
+ {
+ string imageName = GetImage(imageRepoToUse, dockerHelper);
+ return GetProductVersion(imageName, productVersionRepo, dockerHelper);
+ }
- private string FormatDotnetCommand(string command)
+ public string GetProductVersion(string imageName, DotNetImageRepo productVersionRepo, DockerHelper dockerHelper)
+ {
+ string containerName = GetIdentifier($"GetProductVersion-{productVersionRepo}");
+
+ return productVersionRepo switch
{
- // For distroless, dotnet will be the default entrypoint so we don't need to specify "dotnet" in the command.
- // See https://github.com/dotnet/dotnet-docker/issues/3866
- if (IsDistroless)
- {
- return command;
- }
+ DotNetImageRepo.SDK => dockerHelper.Run(imageName, containerName, "dotnet --version"),
+ DotNetImageRepo.Runtime => GetRuntimeVersion(imageName, containerName, "Microsoft.NETCore.App", dockerHelper),
+ DotNetImageRepo.Aspnet => GetRuntimeVersion(imageName, containerName, "Microsoft.AspNetCore.App", dockerHelper),
+ _ => throw new NotSupportedException($"Unsupported image type '{productVersionRepo}'"),
+ };
+ }
- return $"dotnet {command}";
- }
+ private string GetRuntimeVersion(string imageName, string containerName, string runtimeName, DockerHelper dockerHelper)
+ {
+ const string versionGroupName = "Version";
+
+ string runtimeListing = dockerHelper.Run(imageName, containerName, FormatDotnetCommand("--list-runtimes"));
+ Regex versionRegex = new Regex($"{runtimeName} (?<{versionGroupName}>[^\\s]+) ");
+ Match match = versionRegex.Match(runtimeListing);
+ return match.Success ? match.Groups[versionGroupName].Value : string.Empty;
+ }
- private string GetTagName(DotNetImageRepo imageRepo)
+ private string FormatDotnetCommand(string command)
+ {
+ // For distroless, dotnet will be the default entrypoint so we don't need to specify "dotnet" in the command.
+ // See https://github.com/dotnet/dotnet-docker/issues/3866
+ if (IsDistroless)
{
- ImageVersion imageVersion;
- string os;
- string variant = ImageRepoIsSupported(imageRepo) && ImageVariant != DotNetImageVariant.None
- ? GetImageVariantName(ImageVariant)
- : "";
+ return command;
+ }
- switch (imageRepo)
- {
- case DotNetImageRepo.SDK:
- imageVersion = Version;
- os = SdkOS;
- variant = GetImageVariantName(SdkImageVariant);
- break;
- default:
- imageVersion = Version;
- os = OSTag;
- break;
- }
+ return $"dotnet {command}";
+ }
- return GetTagName(imageVersion.GetTagName(), os, variant);
+ private string GetTagName(DotNetImageRepo imageRepo)
+ {
+ ImageVersion imageVersion;
+ string os;
+ string variant = ImageRepoIsSupported(imageRepo) && ImageVariant != DotNetImageVariant.None
+ ? GetImageVariantName(ImageVariant)
+ : "";
+
+ switch (imageRepo)
+ {
+ case DotNetImageRepo.SDK:
+ imageVersion = Version;
+ os = SdkOS;
+ variant = GetImageVariantName(SdkImageVariant);
+ break;
+ default:
+ imageVersion = Version;
+ os = OSTag;
+ break;
}
- public bool ImageRepoIsSupported(DotNetImageRepo imageRepo) => SupportedImageRepos.HasFlag(imageRepo);
+ return GetTagName(imageVersion.GetTagName(), os, variant);
}
+
+ public bool ImageRepoIsSupported(DotNetImageRepo imageRepo) => SupportedImageRepos.HasFlag(imageRepo);
}
diff --git a/tests/Microsoft.DotNet.Docker.Tests/ProductImageTests.cs b/tests/Microsoft.DotNet.Docker.Tests/ProductImageTests.cs
index 1d4b06543b..690137761d 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/ProductImageTests.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/ProductImageTests.cs
@@ -1,433 +1,415 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
-using System.IO;
+using System.Globalization;
using System.Linq;
-using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using FluentAssertions;
using Xunit;
using Xunit.Abstractions;
-namespace Microsoft.DotNet.Docker.Tests
+namespace Microsoft.DotNet.Docker.Tests;
+
+public abstract class ProductImageTests
{
- public abstract class ProductImageTests
+ protected ProductImageTests(ITestOutputHelper outputHelper)
+ {
+ DockerHelper = new DockerHelper(outputHelper);
+ OutputHelper = outputHelper;
+ SyftHelper = new SyftHelper(DockerHelper, OutputHelper);
+ }
+
+ protected DockerHelper DockerHelper { get; }
+ protected ITestOutputHelper OutputHelper { get; }
+ protected SyftHelper SyftHelper { get; }
+ protected abstract DotNetImageRepo ImageRepo { get; }
+
+ ///
+ /// Verifies that a SAS token isn't accidentally leaked through the Docker history of the image.
+ ///
+ protected void VerifyCommonNoSasToken(ProductImageData imageData)
+ {
+ string imageTag = imageData.GetImage(ImageRepo, DockerHelper);
+ string historyContents = DockerHelper.GetHistory(imageTag);
+ Match match = Regex.Match(historyContents, @"=\?(sig|\S+&sig)=\S+");
+ Assert.False(match.Success, $"A SAS token was detected in the Docker history of '{imageTag}'.");
+ }
+
+ protected void VerifyCommonInsecureFiles(ProductImageData imageData)
{
- protected ProductImageTests(ITestOutputHelper outputHelper)
+ if (imageData.OS.Contains("alpine") && imageData.IsArm)
{
- DockerHelper = new DockerHelper(outputHelper);
- OutputHelper = outputHelper;
- SyftHelper = new SyftHelper(DockerHelper, OutputHelper);
+ return;
}
- protected DockerHelper DockerHelper { get; }
- protected ITestOutputHelper OutputHelper { get; }
- protected SyftHelper SyftHelper { get; }
- protected abstract DotNetImageRepo ImageRepo { get; }
-
- ///
- /// Verifies that a SAS token isn't accidentally leaked through the Docker history of the image.
- ///
- protected void VerifyCommonNoSasToken(ProductImageData imageData)
+ if (imageData.OS.Contains("resolute"))
{
- string imageTag = imageData.GetImage(ImageRepo, DockerHelper);
- string historyContents = DockerHelper.GetHistory(imageTag);
- Match match = Regex.Match(historyContents, @"=\?(sig|\S+&sig)=\S+");
- Assert.False(match.Success, $"A SAS token was detected in the Docker history of '{imageTag}'.");
+ OutputHelper.WriteLine("Skipping insecure files check for Ubuntu 26.04 (resolute)."
+ + " Remove this check when https://github.com/dotnet/dotnet-docker/issues/7054 is resolved.");
+ return;
}
- protected void VerifyCommonInsecureFiles(ProductImageData imageData)
+ string rootFsPath = imageData.IsDistroless ? "/rootfs" : "/";
+
+ string worldWritableDirectoriesWithoutStickyBitCmd = $@"find {rootFsPath} -xdev -type d \( -perm -0002 -a ! -perm -1000 \)";
+ string worldWritableFilesCmd = $"find {rootFsPath} -xdev -type f -perm -o+w";
+ string noUserOrGroupFilesCmd;
+ if (imageData.OS.Contains("alpine"))
{
- if (imageData.OS.Contains("alpine") && imageData.IsArm)
- {
- return;
- }
-
- if (imageData.OS.Contains("resolute"))
- {
- OutputHelper.WriteLine("Skipping insecure files check for Ubuntu 26.04 (resolute)."
- + " Remove this check when https://github.com/dotnet/dotnet-docker/issues/7054 is resolved.");
- return;
- }
-
- string rootFsPath = imageData.IsDistroless ? "/rootfs" : "/";
-
- string worldWritableDirectoriesWithoutStickyBitCmd = $@"find {rootFsPath} -xdev -type d \( -perm -0002 -a ! -perm -1000 \)";
- string worldWritableFilesCmd = $"find {rootFsPath} -xdev -type f -perm -o+w";
- string noUserOrGroupFilesCmd;
- if (imageData.OS.Contains("alpine"))
- {
- // BusyBox in Alpine doesn't support the more convenient -nouser and -nogroup options for the find command
- noUserOrGroupFilesCmd = $@"find {rootFsPath} -xdev -exec stat -c %U-%n {{}} \+ | {{ grep ^UNKNOWN || true; }}";
- }
- else
- {
- noUserOrGroupFilesCmd = $@"find {rootFsPath} -xdev \( -nouser -o -nogroup \)";
- }
-
- string command = $"-c \"{worldWritableDirectoriesWithoutStickyBitCmd} && {worldWritableFilesCmd} && {noUserOrGroupFilesCmd}\"";
-
- string imageTag;
- if (imageData.IsDistroless)
- {
- imageTag = DockerHelper.BuildDistrolessHelper(ImageRepo, imageData, rootFsPath);
- }
- else
- {
- imageTag = imageData.GetImage(ImageRepo, DockerHelper);
- }
-
- string output = DockerHelper.Run(
- image: imageTag,
- name: imageData.GetIdentifier($"InsecureFiles-{ImageRepo}"),
- command: command,
- runAsUser: "root",
- optionalRunArgs: "--entrypoint /bin/sh"
- );
-
- string rootFsPathWithTrailingSlash = rootFsPath;
- if (!rootFsPathWithTrailingSlash.EndsWith("/"))
- {
- rootFsPathWithTrailingSlash += "/";
- }
-
- IEnumerable lines = output
- .ReplaceLineEndings()
- .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)
- // Writable files in the /tmp/.dotnet directory are allowed for global mutexes
- .Where(line => !line.StartsWith($"{rootFsPathWithTrailingSlash}tmp/.dotnet"))
- // Exclude the non-root user's home directory. It will show up as a directory without a user or group
- // because we're not examining the distroless container directly, but rather copying its contents into
- // a container with a shell but doesn't have the non-root user defined.
- .Where(line => !imageData.IsDistroless || line != $"{rootFsPathWithTrailingSlash}home/app");
-
- Assert.Empty(lines);
+ // BusyBox in Alpine doesn't support the more convenient -nouser and -nogroup options for the find command
+ noUserOrGroupFilesCmd = $@"find {rootFsPath} -xdev -exec stat -c %U-%n {{}} \+ | {{ grep ^UNKNOWN || true; }}";
+ }
+ else
+ {
+ noUserOrGroupFilesCmd = $@"find {rootFsPath} -xdev \( -nouser -o -nogroup \)";
}
- protected void VerifyCommonDefaultUser(ProductImageData imageData)
+ string command = $"-c \"{worldWritableDirectoriesWithoutStickyBitCmd} && {worldWritableFilesCmd} && {noUserOrGroupFilesCmd}\"";
+
+ string imageTag = imageData.IsDistroless
+ ? DockerHelper.BuildDistrolessHelper(ImageRepo, imageData, rootFsPath)
+ : imageData.GetImage(ImageRepo, DockerHelper);
+ string output = DockerHelper.Run(
+ image: imageTag,
+ name: imageData.GetIdentifier($"InsecureFiles-{ImageRepo}"),
+ command: command,
+ runAsUser: "root",
+ optionalRunArgs: "--entrypoint /bin/sh"
+ );
+
+ string rootFsPathWithTrailingSlash = rootFsPath;
+ if (!rootFsPathWithTrailingSlash.EndsWith('/'))
{
- string imageTag = imageData.GetImage(ImageRepo, DockerHelper);
- string actualUser = DockerHelper.GetImageUser(imageTag);
-
- string expectedUser;
- if (imageData.IsDistroless && ImageRepo != DotNetImageRepo.SDK)
- {
- expectedUser = imageData.NonRootUID.ToString();
- }
+ rootFsPathWithTrailingSlash += "/";
+ }
+
+ IEnumerable lines = output
+ .ReplaceLineEndings()
+ .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)
+ // Writable files in the /tmp/.dotnet directory are allowed for global mutexes
+ .Where(line => !line.StartsWith($"{rootFsPathWithTrailingSlash}tmp/.dotnet", StringComparison.Ordinal))
+ // Exclude the non-root user's home directory. It will show up as a directory without a user or group
+ // because we're not examining the distroless container directly, but rather copying its contents into
+ // a container with a shell but doesn't have the non-root user defined.
+ .Where(line => !imageData.IsDistroless || line != $"{rootFsPathWithTrailingSlash}home/app");
+
+ Assert.Empty(lines);
+ }
+
+ protected void VerifyCommonDefaultUser(ProductImageData imageData)
+ {
+ string imageTag = imageData.GetImage(ImageRepo, DockerHelper);
+ string actualUser = DockerHelper.GetImageUser(imageTag);
+
+ string expectedUser = imageData.IsDistroless && ImageRepo != DotNetImageRepo.SDK
+ ? imageData.NonRootUID?.ToString(CultureInfo.InvariantCulture) ?? string.Empty
// For Windows, only Nano Server defines a user, which seems wrong.
// I've logged https://dev.azure.com/microsoft/OS/_workitems/edit/40146885 for this.
- else if (imageData.OS.Family == OSFamily.NanoServer)
- {
- expectedUser = "ContainerUser";
- }
- else
- {
- expectedUser = string.Empty;
- }
-
- Assert.Equal(expectedUser, actualUser);
-
- VerifyNonRootUID(imageData);
- }
+ : imageData.OS.Family == OSFamily.NanoServer ? "ContainerUser" : string.Empty;
+ Assert.Equal(expectedUser, actualUser);
+
+ VerifyNonRootUID(imageData);
+ }
- protected void VerifyNonRootUID(ProductImageData imageData)
+ protected void VerifyNonRootUID(ProductImageData imageData)
+ {
+ if (imageData.IsWindows)
{
- if (imageData.IsWindows)
- {
- OutputHelper.WriteLine("UID check is only relevant for Linux images");
- return;
- }
-
- string imageTag = imageData.GetImage(ImageRepo, DockerHelper);
- string rootPath = "/";
-
- if (imageData.IsDistroless)
- {
- rootPath = "/rootfs/";
- imageTag = DockerHelper.BuildDistrolessHelper(ImageRepo, imageData, rootPath);
- }
-
- string command = $"-c \"grep '^app' {rootPath}etc/passwd | cut -d: -f3\"";
-
- string uidString = DockerHelper.Run(
- image: imageTag,
- command: command,
- name: imageData.GetIdentifier($"VerifyUID-{ImageRepo}"),
- optionalRunArgs: $"--entrypoint /bin/sh"
- );
-
- int uid = int.Parse(uidString);
-
- // UIDs below 1000 are reserved for system accounts
- Assert.True(uid >= 1000);
- // Debian has a UID_MAX of 60000
- Assert.True(uid <= 60000);
+ OutputHelper.WriteLine("UID check is only relevant for Linux images");
+ return;
}
- public static IEnumerable GetCommonEnvironmentVariables()
+ string imageTag = imageData.GetImage(ImageRepo, DockerHelper);
+ string rootPath = "/";
+
+ if (imageData.IsDistroless)
{
- yield return EnvironmentVariableInfo.Require("DOTNET_RUNNING_IN_CONTAINER", "true");
+ rootPath = "/rootfs/";
+ imageTag = DockerHelper.BuildDistrolessHelper(ImageRepo, imageData, rootPath);
}
- ///
- /// Verifies that the packages installed are correct and scannable by security tools.
- ///
- internal void VerifyInstalledPackagesBase(
- ProductImageData imageData,
- DotNetImageRepo imageRepo,
- IEnumerable extraExcludePaths = null)
+ string command = $"-c \"grep '^app' {rootPath}etc/passwd | cut -d: -f3\"";
+
+ string uidString = DockerHelper.Run(
+ image: imageTag,
+ command: command,
+ name: imageData.GetIdentifier($"VerifyUID-{ImageRepo}"),
+ optionalRunArgs: $"--entrypoint /bin/sh"
+ );
+
+ int uid = int.Parse(uidString, CultureInfo.InvariantCulture);
+
+ // UIDs below 1000 are reserved for system accounts
+ Assert.True(uid >= 1000);
+ // Debian has a UID_MAX of 60000
+ Assert.True(uid <= 60000);
+ }
+
+ public static IEnumerable GetCommonEnvironmentVariables()
+ {
+ yield return EnvironmentVariableInfo.Require("DOTNET_RUNNING_IN_CONTAINER", "true");
+ }
+
+ ///
+ /// Verifies that the packages installed are correct and scannable by security tools.
+ ///
+ internal void VerifyInstalledPackagesBase(
+ ProductImageData imageData,
+ DotNetImageRepo imageRepo,
+ IEnumerable extraExcludePaths = null)
+ {
+ if (imageData.OS.IsUnstable)
{
- if (imageData.OS.IsUnstable)
- {
- OutputHelper.WriteLine(
- $"Skipping installed-packages verification for unstable OS '{imageData.OS.DisplayName}'."
- + " The package set for pre-release distros is expected to change and is not asserted by tests.");
- return;
- }
-
- if (imageData.OS == OS.ResoluteChiseled)
- {
- OutputHelper.WriteLine(
- "Temporarily skipping installed-packages verification for Ubuntu 26.04 chiseled images."
- + " Re-enable when images include the fix for https://github.com/canonical/rocks-toolbox/issues/52.");
- return;
- }
-
- IEnumerable expectedPackages = GetExpectedPackages(imageData, imageRepo);
- IEnumerable actualPackages = GetInstalledPackages(imageData, imageRepo, extraExcludePaths);
-
- string imageName = imageData.GetImage(imageRepo, DockerHelper, skipPull: true);
- ComparePackages(expectedPackages, actualPackages, imageData.IsDistroless, imageName, OutputHelper);
+ OutputHelper.WriteLine(
+ $"Skipping installed-packages verification for unstable OS '{imageData.OS.DisplayName}'."
+ + " The package set for pre-release distros is expected to change and is not asserted by tests.");
+ return;
}
- internal static void ComparePackages(
- IEnumerable expectedPackages,
- IEnumerable actualPackages,
- bool isDistroless,
- string imageName,
- ITestOutputHelper outputHelper)
+ if (imageData.OS == OS.ResoluteChiseled)
{
- outputHelper.WriteLine($"Expected Packages: [ {string.Join(", ", expectedPackages)} ]");
- outputHelper.WriteLine($"Actual Packages: [ {string.Join(", ", actualPackages)} ]");
-
- // Verify we only include strictly necessary packages in distroless images
- if (isDistroless)
- {
- actualPackages.Should().BeEquivalentTo(expectedPackages, because: $"image {imageName} is distroless");
- return;
- }
-
- // Verify satisfy .NET dependencies on non-distroless images.
- // There will be additional packages from the distro.
- expectedPackages.Should().BeSubsetOf(actualPackages, because: $"image {imageName} is not distroless");
+ OutputHelper.WriteLine(
+ "Temporarily skipping installed-packages verification for Ubuntu 26.04 chiseled images."
+ + " Re-enable when images include the fix for https://github.com/canonical/rocks-toolbox/issues/52.");
+ return;
}
- ///
- /// Gets a list of all unique linux packages in the image.
- ///
- ///
- /// These paths will be excluded from image scanning.
- ///
- ///
- internal IEnumerable GetInstalledPackages(
- ProductImageData imageData,
- DotNetImageRepo imageRepo,
- IEnumerable extraExcludePaths = null)
+ IEnumerable expectedPackages = GetExpectedPackages(imageData, imageRepo);
+ IEnumerable actualPackages = GetInstalledPackages(imageData, imageRepo, extraExcludePaths);
+
+ string imageName = imageData.GetImage(imageRepo, DockerHelper, skipPull: true);
+ ComparePackages(expectedPackages, actualPackages, imageData.IsDistroless, imageName, OutputHelper);
+ }
+
+ internal static void ComparePackages(
+ IEnumerable expectedPackages,
+ IEnumerable actualPackages,
+ bool isDistroless,
+ string imageName,
+ ITestOutputHelper outputHelper)
+ {
+ outputHelper.WriteLine($"Expected Packages: [ {string.Join(", ", expectedPackages)} ]");
+ outputHelper.WriteLine($"Actual Packages: [ {string.Join(", ", actualPackages)} ]");
+
+ // Verify we only include strictly necessary packages in distroless images
+ if (isDistroless)
{
- JsonNode output = SyftHelper.Scan(imageData, imageRepo, extraExcludePaths);
-
- return ((JsonArray)output["artifacts"])
- .Select(artifact => artifact["name"]?.ToString())
- // Syft can sometimes detect duplicates of packages if they have a distro-specific version suffix. Syft
- // will report the distro package version and the binary package version as separate.
- //
- // openssl 1.1.1k binary
- // openssl 1.1.1k-29.azl3 rpm
- // ^ Azure Linux specific version
- //
- // So we need to remove duplicates.
- .Distinct();
+ actualPackages.Should().BeEquivalentTo(expectedPackages, because: $"image {imageName} is distroless");
+ return;
}
- internal static IEnumerable GetExpectedPackages(ProductImageData imageData, DotNetImageRepo imageRepo)
- {
- IEnumerable expectedPackages = GetRuntimeDepsPackages(imageData);
+ // Verify satisfy .NET dependencies on non-distroless images.
+ // There will be additional packages from the distro.
+ expectedPackages.Should().BeSubsetOf(actualPackages, because: $"image {imageName} is not distroless");
+ }
- if (imageData.IsDistroless)
- {
- expectedPackages = [..expectedPackages, ..GetDistrolessBasePackages(imageData)];
- }
+ ///
+ /// Gets a list of all unique linux packages in the image.
+ ///
+ /// The image data of the image to scan.
+ /// The repository of the image to scan.
+ ///
+ /// These paths will be excluded from image scanning.
+ ///
+ ///
+ internal IEnumerable GetInstalledPackages(
+ ProductImageData imageData,
+ DotNetImageRepo imageRepo,
+ IEnumerable extraExcludePaths = null)
+ {
+ JsonNode output = SyftHelper.Scan(imageData, imageRepo, extraExcludePaths);
+
+ return ((JsonArray)output["artifacts"])
+ .Select(artifact => artifact["name"]?.ToString())
+ // Syft can sometimes detect duplicates of packages if they have a distro-specific version suffix. Syft
+ // will report the distro package version and the binary package version as separate.
+ //
+ // openssl 1.1.1k binary
+ // openssl 1.1.1k-29.azl3 rpm
+ // ^ Azure Linux specific version
+ //
+ // So we need to remove duplicates.
+ .Distinct();
+ }
- if (imageData.ImageVariant.HasFlag(DotNetImageVariant.Extra) || imageRepo == DotNetImageRepo.SDK)
- {
- expectedPackages = [..expectedPackages, ..GetExtraPackages(imageData)];
- }
+ internal static IEnumerable GetExpectedPackages(ProductImageData imageData, DotNetImageRepo imageRepo)
+ {
+ IEnumerable expectedPackages = GetRuntimeDepsPackages(imageData);
- return expectedPackages.Distinct().OrderBy(s => s);
+ if (imageData.IsDistroless)
+ {
+ expectedPackages = [.. expectedPackages, .. GetDistrolessBasePackages(imageData)];
}
- private static IEnumerable GetDistrolessBasePackages(ProductImageData imageData) => imageData switch
- {
- { OS.Family: OSFamily.AzureLinux } => new[]
- {
- "SymCrypt",
- "SymCrypt-OpenSSL",
- "azurelinux-release",
- "distroless-packages-minimal",
- "filesystem",
- "prebuilt-ca-certificates",
- "tzdata"
- },
- { OS.Family: OSFamily.Ubuntu } => new[]
- {
- "base-files"
- },
- _ => throw new NotSupportedException($"Unsupported distroless OS '{imageData.OS}'")
- };
-
- private static IEnumerable GetRuntimeDepsPackages(ProductImageData imageData) {
- IEnumerable packages = imageData switch
- {
- { OS.Family: OSFamily.AzureLinux } =>
- [
- "glibc",
- "libgcc",
- "openssl",
- "openssl-libs",
- "libstdc++"
- ],
- { OS: { Family: OSFamily.Ubuntu, Version: "22.04"} } =>
- [
- "ca-certificates",
- "gcc-12-base",
- "libc6",
- "libgcc-s1",
- "libssl3",
- "openssl",
- "libstdc++6"
- ],
- { OS: var os } when os == OS.ResoluteChiseled =>
- [
- "ca-certificates",
- "gcc-16-base",
- ..GetResoluteChiseledArchSpecificPackages(imageData),
- "libc6",
- "libgcc-s1",
- "libssl3t64",
- "libstdc++6",
- "libzstd",
- "libzstd1",
- "openssl",
- "zlib",
- "zlib1g"
- ],
- { OS: { Family: OSFamily.Ubuntu, Version: "26.04" } } =>
- [
- "ca-certificates",
- "gcc-16-base",
- "libc6",
- "libgcc-s1",
- "libssl3t64",
- "openssl",
- "libstdc++6"
- ],
- { OS: var os } when os == OS.NobleChiseled =>
- [
- "ca-certificates",
- "gcc-14-base",
- "gcc-14",
- "libc6",
- "libgcc-s1",
- "libssl3t64",
- "openssl",
- "libstdc++6"
- ],
- { OS: { Family: OSFamily.Ubuntu, Version: "24.04" } } =>
- [
- "ca-certificates",
- "gcc-14-base",
- "libc6",
- "libgcc-s1",
- "libssl3t64",
- "openssl",
- "libstdc++6"
- ],
- { OS.Family: OSFamily.Alpine } =>
- [
- "ca-certificates-bundle",
- "libgcc",
- "libssl3",
- "libstdc++"
- ],
- { OS: var os } when os == OS.BookwormSlim =>
- [
- "ca-certificates",
- "libc6",
- "libgcc-s1",
- "libicu72",
- "libssl3",
- "tzdata",
- "libstdc++6"
- ],
- _ => throw new NotSupportedException($"Unknown runtime-deps packages for OS '{imageData.OS}'")
- };
-
- // zlib is not required for .NET 9+
- // - https://github.com/dotnet/dotnet-docker/issues/5687
- if (imageData.Version.Major == 8)
- {
- packages = [..packages, GetZLibPackage(imageData.OS)];
- }
-
- return packages;
+ if (imageData.ImageVariant.HasFlag(DotNetImageVariant.Extra) || imageRepo == DotNetImageRepo.SDK)
+ {
+ expectedPackages = [.. expectedPackages, .. GetExtraPackages(imageData)];
}
- private static string GetZLibPackage(OSInfo os)
+ return expectedPackages.Distinct().OrderBy(s => s);
+ }
+
+ private static string[] GetDistrolessBasePackages(ProductImageData imageData) => imageData switch
+ {
+ { OS.Family: OSFamily.AzureLinux } =>
+ [
+ "SymCrypt",
+ "SymCrypt-OpenSSL",
+ "azurelinux-release",
+ "distroless-packages-minimal",
+ "filesystem",
+ "prebuilt-ca-certificates",
+ "tzdata"
+ ],
+ { OS.Family: OSFamily.Ubuntu } =>
+ [
+ "base-files"
+ ],
+ _ => throw new NotSupportedException($"Unsupported distroless OS '{imageData.OS}'")
+ };
+
+ private static IEnumerable GetRuntimeDepsPackages(ProductImageData imageData)
+ {
+ IEnumerable packages = imageData switch
+ {
+ { OS.Family: OSFamily.AzureLinux } =>
+ [
+ "glibc",
+ "libgcc",
+ "openssl",
+ "openssl-libs",
+ "libstdc++"
+ ],
+ { OS: { Family: OSFamily.Ubuntu, Version: "22.04" } } =>
+ [
+ "ca-certificates",
+ "gcc-12-base",
+ "libc6",
+ "libgcc-s1",
+ "libssl3",
+ "openssl",
+ "libstdc++6"
+ ],
+ { OS: var os } when os == OS.ResoluteChiseled =>
+ [
+ "ca-certificates",
+ "gcc-16-base",
+ ..GetResoluteChiseledArchSpecificPackages(imageData),
+ "libc6",
+ "libgcc-s1",
+ "libssl3t64",
+ "libstdc++6",
+ "libzstd",
+ "libzstd1",
+ "openssl",
+ "zlib",
+ "zlib1g"
+ ],
+ { OS: { Family: OSFamily.Ubuntu, Version: "26.04" } } =>
+ [
+ "ca-certificates",
+ "gcc-16-base",
+ "libc6",
+ "libgcc-s1",
+ "libssl3t64",
+ "openssl",
+ "libstdc++6"
+ ],
+ { OS: var os } when os == OS.NobleChiseled =>
+ [
+ "ca-certificates",
+ "gcc-14-base",
+ "gcc-14",
+ "libc6",
+ "libgcc-s1",
+ "libssl3t64",
+ "openssl",
+ "libstdc++6"
+ ],
+ { OS: { Family: OSFamily.Ubuntu, Version: "24.04" } } =>
+ [
+ "ca-certificates",
+ "gcc-14-base",
+ "libc6",
+ "libgcc-s1",
+ "libssl3t64",
+ "openssl",
+ "libstdc++6"
+ ],
+ { OS.Family: OSFamily.Alpine } =>
+ [
+ "ca-certificates-bundle",
+ "libgcc",
+ "libssl3",
+ "libstdc++"
+ ],
+ { OS: var os } when os == OS.BookwormSlim =>
+ [
+ "ca-certificates",
+ "libc6",
+ "libgcc-s1",
+ "libicu72",
+ "libssl3",
+ "tzdata",
+ "libstdc++6"
+ ],
+ _ => throw new NotSupportedException($"Unknown runtime-deps packages for OS '{imageData.OS}'")
+ };
+
+ // zlib is not required for .NET 9+
+ // - https://github.com/dotnet/dotnet-docker/issues/5687
+ if (imageData.Version.Major == 8)
{
- OSFamily[] unversionedZLibOSFamilies = [OSFamily.Alpine, OSFamily.AzureLinux];
- return unversionedZLibOSFamilies.Contains(os.Family) ? "zlib" : "zlib1g";
+ packages = [.. packages, GetZLibPackage(imageData.OS)];
}
- ///
- /// Syft detects gcc-16 as an additional binary package on amd64 resolute chiseled images.
- ///
- private static IEnumerable GetResoluteChiseledArchSpecificPackages(ProductImageData imageData) =>
- imageData.Arch == Arch.Amd64 ? ["gcc-16"] : [];
-
- private static IEnumerable GetExtraPackages(ProductImageData imageData) => imageData switch
- {
- { IsDistroless: true, OS.Family: OSFamily.AzureLinux } => new[]
- {
- "icu",
- "tzdata"
- },
- { OS: var os } when os == OS.ResoluteChiseled => new[]
- {
- "icu",
- "libicu78",
- "tzdata"
- },
- { OS: var os } when os == OS.NobleChiseled => new[]
- {
- "libicu74",
- "tzdata"
- },
- { OS: var os } when os == OS.JammyChiseled => new[]
- {
- "libicu70",
- "tzdata"
- },
- { OS.Family: OSFamily.Alpine } => new[]
- {
- "icu-data-full",
- "icu-libs",
- "tzdata"
- },
- _ => Array.Empty()
- };
+ return packages;
}
+
+ private static string GetZLibPackage(OSInfo os)
+ {
+ OSFamily[] unversionedZLibOSFamilies = [OSFamily.Alpine, OSFamily.AzureLinux];
+ return unversionedZLibOSFamilies.Contains(os.Family) ? "zlib" : "zlib1g";
+ }
+
+ ///
+ /// Syft detects gcc-16 as an additional binary package on amd64 resolute chiseled images.
+ ///
+ private static IEnumerable GetResoluteChiseledArchSpecificPackages(ProductImageData imageData) =>
+ imageData.Arch == Arch.Amd64 ? ["gcc-16"] : [];
+
+ private static string[] GetExtraPackages(ProductImageData imageData) => imageData switch
+ {
+ { IsDistroless: true, OS.Family: OSFamily.AzureLinux } =>
+ [
+ "icu",
+ "tzdata"
+ ],
+ { OS: var os } when os == OS.ResoluteChiseled =>
+ [
+ "icu",
+ "libicu78",
+ "tzdata"
+ ],
+ { OS: var os } when os == OS.NobleChiseled =>
+ [
+ "libicu74",
+ "tzdata"
+ ],
+ { OS: var os } when os == OS.JammyChiseled =>
+ [
+ "libicu70",
+ "tzdata"
+ ],
+ { OS.Family: OSFamily.Alpine } =>
+ [
+ "icu-data-full",
+ "icu-libs",
+ "tzdata"
+ ],
+ _ => []
+ };
}
diff --git a/tests/Microsoft.DotNet.Docker.Tests/RuntimeDepsImageTests.cs b/tests/Microsoft.DotNet.Docker.Tests/RuntimeDepsImageTests.cs
index 4903389b32..ff3ed9c4c7 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/RuntimeDepsImageTests.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/RuntimeDepsImageTests.cs
@@ -1,184 +1,159 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
+using Microsoft.DotNet.Docker.Tests.TestScenarios;
using Shouldly;
using Xunit;
using Xunit.Abstractions;
-namespace Microsoft.DotNet.Docker.Tests
-{
- [Trait("Category", "runtime-deps")]
- public class RuntimeDepsImageTests : CommonRuntimeImageTests
- {
- public RuntimeDepsImageTests(ITestOutputHelper outputHelper)
- : base(outputHelper)
- {
- }
+namespace Microsoft.DotNet.Docker.Tests;
- protected override DotNetImageRepo ImageRepo => DotNetImageRepo.Runtime_Deps;
+[Trait("Category", "runtime-deps")]
+public class RuntimeDepsImageTests(ITestOutputHelper outputHelper) : CommonRuntimeImageTests(outputHelper)
+{
+ protected override DotNetImageRepo ImageRepo => DotNetImageRepo.Runtime_Deps;
- public static IEnumerable GetImageData() =>
- GetImageData(DotNetImageRepo.Runtime_Deps);
+ public static IEnumerable GetImageData() =>
+ GetImageData(DotNetImageRepo.Runtime_Deps);
- public static IEnumerable GetAotImageData() =>
- GetImageData(DotNetImageRepo.Runtime_Deps, DotNetImageVariant.AOT);
+ public static IEnumerable GetAotImageData() =>
+ GetImageData(DotNetImageRepo.Runtime_Deps, DotNetImageVariant.AOT);
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public async Task VerifySelfContainedConsoleScenario(ProductImageData imageData)
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public async Task VerifySelfContainedConsoleScenario(ProductImageData imageData)
+ {
+ if (imageData.ImageVariant.HasFlag(DotNetImageVariant.AOT))
{
- if (imageData.ImageVariant.HasFlag(DotNetImageVariant.AOT))
- {
- OutputHelper.WriteLine(
- $"Test is not applicable to AOT images. See {nameof(VerifyAotWebScenario)} instead.");
- return;
- }
-
- ConsoleAppScenario testScenario =
- new ConsoleAppScenario.SelfContained(imageData, DockerHelper, OutputHelper);
- await testScenario.ExecuteAsync();
+ OutputHelper.WriteLine(
+ $"Test is not applicable to AOT images. See {nameof(VerifyAotWebScenario)} instead.");
+ return;
}
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public async Task VerifySelfContainedWebScenario(ProductImageData imageData)
- {
- if (imageData.ImageVariant.HasFlag(DotNetImageVariant.AOT))
- {
- OutputHelper.WriteLine(
- $"Test is not applicable to AOT images. See {nameof(VerifyAotWebScenario)} instead.");
- return;
- }
-
- WebScenario testScenario = new WebScenario.SelfContained(imageData, DockerHelper, OutputHelper);
- await testScenario.ExecuteAsync();
- }
+ ConsoleAppScenario testScenario =
+ new ConsoleAppScenario.SelfContained(imageData, DockerHelper, OutputHelper);
+ await testScenario.ExecuteAsync();
+ }
- [LinuxImageTheory]
- [MemberData(nameof(GetAotImageData))]
- public async Task VerifyAotWebScenario(ProductImageData imageData)
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public async Task VerifySelfContainedWebScenario(ProductImageData imageData)
+ {
+ if (imageData.ImageVariant.HasFlag(DotNetImageVariant.AOT))
{
- if (imageData.Arch == Arch.Arm)
- {
- OutputHelper.WriteLine("Skipping test due to https://github.com/dotnet/docker-tools/issues/1177. "
- + "ImageBuilder is unable to queue arm32 AOT images together with the arm64 AOT SDKs. "
- + "Re-enable once fixed.");
- return;
- }
-
- WebScenario scenario = new WebScenario.Aot(imageData, DockerHelper, OutputHelper);
- await scenario.ExecuteAsync();
+ OutputHelper.WriteLine(
+ $"Test is not applicable to AOT images. See {nameof(VerifyAotWebScenario)} instead.");
+ return;
}
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyEnvironmentVariables(ProductImageData imageData)
- {
- base.VerifyCommonEnvironmentVariables(imageData);
- }
+ WebScenario testScenario = new WebScenario.SelfContained(imageData, DockerHelper, OutputHelper);
+ await testScenario.ExecuteAsync();
+ }
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyInsecureFiles(ProductImageData imageData)
+ [LinuxImageTheory]
+ [MemberData(nameof(GetAotImageData))]
+ public async Task VerifyAotWebScenario(ProductImageData imageData)
+ {
+ if (imageData.Arch == Arch.Arm)
{
- base.VerifyCommonInsecureFiles(imageData);
+ OutputHelper.WriteLine("Skipping test due to https://github.com/dotnet/docker-tools/issues/1177. "
+ + "ImageBuilder is unable to queue arm32 AOT images together with the arm64 AOT SDKs. "
+ + "Re-enable once fixed.");
+ return;
}
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyShellNotInstalledForDistroless(ProductImageData imageData)
- {
- base.VerifyCommonShellNotInstalledForDistroless(imageData);
- }
+ WebScenario scenario = new WebScenario.Aot(imageData, DockerHelper, OutputHelper);
+ await scenario.ExecuteAsync();
+ }
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyNoSasToken(ProductImageData imageData)
- {
- base.VerifyCommonNoSasToken(imageData);
- }
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyEnvironmentVariables(ProductImageData imageData) => VerifyCommonEnvironmentVariables(imageData);
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyDefaultUser(ProductImageData imageData)
- {
- VerifyCommonDefaultUser(imageData);
- }
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyInsecureFiles(ProductImageData imageData) => VerifyCommonInsecureFiles(imageData);
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyDistrolessOSReleaseInfo(ProductImageData imageData)
- {
- if (!imageData.IsDistroless)
- {
- OutputHelper.WriteLine("This test is only relevant to distroless images.");
- return;
- }
-
- var osReleaseInfo = GetOSReleaseInfo(imageData, ImageRepo);
- OutputHelper.WriteLine($"OS Release Info: {osReleaseInfo}");
- osReleaseInfo.ShouldNotBeEmpty();
- }
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyShellNotInstalledForDistroless(ProductImageData imageData) => VerifyCommonShellNotInstalledForDistroless(imageData);
+
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyNoSasToken(ProductImageData imageData) => VerifyCommonNoSasToken(imageData);
- ///
- /// Verifies the presence of the Chisel manifest in Ubuntu Chiseled images. Chisel manifest documentation:
- /// https://discourse.ubuntu.com/t/chisel-manifest-is-supported-in-newly-released-v1-0-0/48944
- ///
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyChiselManifest(ProductImageData imageData)
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyDefaultUser(ProductImageData imageData) => VerifyCommonDefaultUser(imageData);
+
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyDistrolessOSReleaseInfo(ProductImageData imageData)
+ {
+ if (!imageData.IsDistroless)
{
- if (!(imageData.OS.Family == OSFamily.Ubuntu && imageData.IsDistroless))
- {
- OutputHelper.WriteLine("Test is only relevant to Ubuntu Chiseled images");
- return;
- }
-
- // https://github.com/dotnet/dotnet-docker/issues/5973#issuecomment-2501510550
- bool shouldContainManifest = imageData.Version.Major != 8 && imageData.Version.Major != 9;
-
- const string RootFs = "/rootfs";
- const string ChiselManifestFileName = "/var/lib/chisel/manifest.wall";
-
- // Setup a distroless helper image to inspect the filesystem of the Chiseled image
- string distrolessHelperImageTag = DockerHelper.BuildDistrolessHelper(ImageRepo, imageData, RootFs);
-
- // Check for the presence of the Chisel manifest by listing the files in the directory
- // and then verifying the output.
- string actualOutput = DockerHelper.Run(
- image: distrolessHelperImageTag,
- name: imageData.GetIdentifier(nameof(VerifyChiselManifest)),
- command: $"find {RootFs}/var/lib/ -path *chisel* -type f");
-
- if (shouldContainManifest)
- {
- Assert.Contains(ChiselManifestFileName, actualOutput);
- }
- else
- {
- Assert.DoesNotContain(ChiselManifestFileName, actualOutput);
- }
+ OutputHelper.WriteLine("This test is only relevant to distroless images.");
+ return;
}
- private string GetOSReleaseInfo(
- ProductImageData imageData,
- DotNetImageRepo imageRepo)
+ string osReleaseInfo = GetOSReleaseInfo(imageData, ImageRepo);
+ OutputHelper.WriteLine($"OS Release Info: {osReleaseInfo}");
+ osReleaseInfo.ShouldNotBeEmpty();
+ }
+
+ ///
+ /// Verifies the presence of the Chisel manifest in Ubuntu Chiseled images. Chisel manifest documentation:
+ /// https://discourse.ubuntu.com/t/chisel-manifest-is-supported-in-newly-released-v1-0-0/48944
+ ///
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyChiselManifest(ProductImageData imageData)
+ {
+ if (!(imageData.OS.Family == OSFamily.Ubuntu && imageData.IsDistroless))
{
- JsonNode output = SyftHelper.Scan(imageData, imageRepo);
- JsonObject distro = (JsonObject)output["distro"];
- return (string)distro["version"];
+ OutputHelper.WriteLine("Test is only relevant to Ubuntu Chiseled images");
+ return;
}
+ // https://github.com/dotnet/dotnet-docker/issues/5973#issuecomment-2501510550
+ bool shouldContainManifest = imageData.Version.Major is not 8 and not 9;
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyInstalledPackages(ProductImageData imageData)
+ const string rootFs = "/rootfs";
+ const string chiselManifestFileName = "/var/lib/chisel/manifest.wall";
+
+ // Setup a distroless helper image to inspect the filesystem of the Chiseled image
+ string distrolessHelperImageTag = DockerHelper.BuildDistrolessHelper(ImageRepo, imageData, rootFs);
+
+ // Check for the presence of the Chisel manifest by listing the files in the directory
+ // and then verifying the output.
+ string actualOutput = DockerHelper.Run(
+ image: distrolessHelperImageTag,
+ name: imageData.GetIdentifier(nameof(VerifyChiselManifest)),
+ command: $"find {rootFs}/var/lib/ -path *chisel* -type f");
+
+ if (shouldContainManifest)
+ {
+ Assert.Contains(chiselManifestFileName, actualOutput);
+ }
+ else
{
- VerifyInstalledPackagesBase(imageData, ImageRepo);
+ Assert.DoesNotContain(chiselManifestFileName, actualOutput);
}
}
+
+ private string GetOSReleaseInfo(
+ ProductImageData imageData,
+ DotNetImageRepo imageRepo)
+ {
+ JsonNode output = SyftHelper.Scan(imageData, imageRepo);
+ JsonObject distro = (JsonObject)output["distro"];
+ return (string)distro["version"];
+ }
+
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyInstalledPackages(ProductImageData imageData) => VerifyInstalledPackagesBase(imageData, ImageRepo);
}
diff --git a/tests/Microsoft.DotNet.Docker.Tests/RuntimeImageTests.cs b/tests/Microsoft.DotNet.Docker.Tests/RuntimeImageTests.cs
index cbfc7a7522..ace91def95 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/RuntimeImageTests.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/RuntimeImageTests.cs
@@ -1,110 +1,86 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-using System;
using System.Collections.Generic;
-using System.Linq;
using System.Threading.Tasks;
using Microsoft.DotNet.Docker.Tests.TestScenarios;
using Xunit;
using Xunit.Abstractions;
-namespace Microsoft.DotNet.Docker.Tests
+namespace Microsoft.DotNet.Docker.Tests;
+
+[Trait("Category", "runtime")]
+public class RuntimeImageTests(ITestOutputHelper outputHelper) : CommonRuntimeImageTests(outputHelper)
{
- [Trait("Category", "runtime")]
- public class RuntimeImageTests : CommonRuntimeImageTests
+ protected override DotNetImageRepo ImageRepo => DotNetImageRepo.Runtime;
+
+ public static IEnumerable GetImageData() => GetImageData(DotNetImageRepo.Runtime);
+
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public async Task VerifyFxDependentAppScenario(ProductImageData imageData)
{
- public RuntimeImageTests(ITestOutputHelper outputHelper)
- : base(outputHelper)
- {
- }
+ ConsoleAppScenario testScenario = new ConsoleAppScenario.FxDependent(imageData, DockerHelper, OutputHelper);
+ await testScenario.ExecuteAsync();
+ }
- protected override DotNetImageRepo ImageRepo => DotNetImageRepo.Runtime;
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public async Task VerifyTestProjectScenario(ProductImageData imageData)
+ {
+ ConsoleAppScenario testScenario = new ConsoleAppScenario.TestProject(imageData, DockerHelper, OutputHelper);
+ await testScenario.ExecuteAsync();
+ }
- public static IEnumerable GetImageData() => GetImageData(DotNetImageRepo.Runtime);
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public async Task VerifyGlobalizationScenario(ProductImageData imageData) =>
+ await VerifyGlobalizationScenarioBase(imageData);
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public async Task VerifyFxDependentAppScenario(ProductImageData imageData)
- {
- ConsoleAppScenario testScenario = new ConsoleAppScenario.FxDependent(imageData, DockerHelper, OutputHelper);
- await testScenario.ExecuteAsync();
- }
+ [WindowsImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public async Task VerifyNLSScenario(ProductImageData imageData) =>
+ await VerifyNlsScenarioBase(imageData);
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public async Task VerifyTestProjectScenario(ProductImageData imageData)
- {
- ConsoleAppScenario testScenario = new ConsoleAppScenario.TestProject(imageData, DockerHelper, OutputHelper);
- await testScenario.ExecuteAsync();
- }
-
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public async Task VerifyGlobalizationScenario(ProductImageData imageData) =>
- await VerifyGlobalizationScenarioBase(imageData);
-
- [WindowsImageTheory]
- [MemberData(nameof(GetImageData))]
- public async Task VerifyNLSScenario(ProductImageData imageData) =>
- await VerifyNlsScenarioBase(imageData);
-
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyEnvironmentVariables(ProductImageData imageData)
- {
- List variables = new List
- {
- GetRuntimeVersionVariableInfo(ImageRepo, imageData, DockerHelper)
- };
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyEnvironmentVariables(ProductImageData imageData)
+ {
+ List variables =
+ [
+ GetRuntimeVersionVariableInfo(ImageRepo, imageData, DockerHelper)
+ ];
- base.VerifyCommonEnvironmentVariables(imageData, variables);
- }
+ VerifyCommonEnvironmentVariables(imageData, variables);
+ }
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyInstalledPackages(ProductImageData imageData)
- {
- VerifyInstalledPackagesBase(imageData, ImageRepo);
- }
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyInstalledPackages(ProductImageData imageData) => VerifyInstalledPackagesBase(imageData, ImageRepo);
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyInsecureFiles(ProductImageData imageData)
- {
- base.VerifyCommonInsecureFiles(imageData);
- }
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyInsecureFiles(ProductImageData imageData) => VerifyCommonInsecureFiles(imageData);
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyShellNotInstalledForDistroless(ProductImageData imageData)
- {
- base.VerifyCommonShellNotInstalledForDistroless(imageData);
- }
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyShellNotInstalledForDistroless(ProductImageData imageData) => VerifyCommonShellNotInstalledForDistroless(imageData);
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyNoSasToken(ProductImageData imageData)
- {
- base.VerifyCommonNoSasToken(imageData);
- }
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyNoSasToken(ProductImageData imageData) => VerifyCommonNoSasToken(imageData);
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyDefaultUser(ProductImageData imageData)
- {
- VerifyCommonDefaultUser(imageData);
- }
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyDefaultUser(ProductImageData imageData) => VerifyCommonDefaultUser(imageData);
- public static EnvironmentVariableInfo GetRuntimeVersionVariableInfo(
- DotNetImageRepo imageRepo, ProductImageData imageData, DockerHelper dockerHelper)
+ public static EnvironmentVariableInfo GetRuntimeVersionVariableInfo(
+ DotNetImageRepo imageRepo, ProductImageData imageData, DockerHelper dockerHelper)
+ {
+ string version = imageData.GetProductVersion(imageRepo, DotNetImageRepo.Runtime, dockerHelper);
+ return EnvironmentVariableInfo.Require("DOTNET_VERSION", version) with
{
- string version = imageData.GetProductVersion(imageRepo, DotNetImageRepo.Runtime, dockerHelper);
- return EnvironmentVariableInfo.Require("DOTNET_VERSION", version) with
- {
- IsProductVersion = true
- };
- }
+ IsProductVersion = true
+ };
}
}
diff --git a/tests/Microsoft.DotNet.Docker.Tests/SampleImageData.cs b/tests/Microsoft.DotNet.Docker.Tests/SampleImageData.cs
index 006ff5ef3c..9be7b9d4ee 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/SampleImageData.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/SampleImageData.cs
@@ -1,50 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System;
-namespace Microsoft.DotNet.Docker.Tests
-{
- public record SampleImageData : ImageData
- {
- ///
- /// Gets or sets a value indicating that this sample is published as a Docker image.
- ///
- public bool IsPublished { get; set; }
+namespace Microsoft.DotNet.Docker.Tests;
- public string DockerfileSuffix { get; set; }
+public record SampleImageData : ImageData
+{
+ ///
+ /// Gets or sets a value indicating that this sample is published as a Docker image.
+ ///
+ public bool IsPublished { get; set; }
- public string GetImage(SampleImageType imageType, DockerHelper dockerHelper, bool allowPull = false)
- {
- string tagPrefix = GetTagNameBase(imageType);
- string os = OS;
+ public string DockerfileSuffix { get; set; }
- string tag = GetTagName(tagPrefix, os);
- if (!IsPublished)
- {
- tag += "-local";
- }
+ public string GetImage(SampleImageType imageType, DockerHelper dockerHelper, bool allowPull = false)
+ {
+ string tagPrefix = GetTagNameBase(imageType);
+ string os = OS;
- return GetImage(tag, dockerHelper, allowPull);
+ string tag = GetTagName(tagPrefix, os);
+ if (!IsPublished)
+ {
+ tag += "-local";
}
- public string GetImage(string tag, DockerHelper dockerHelper, bool allowPull = false)
- {
- string imageName = GetImageName(tag);
+ return GetImage(tag, dockerHelper, allowPull);
+ }
- if (IsPublished)
- {
- PullImageIfNecessary(imageName, dockerHelper, allowPull);
- }
+ public string GetImage(string tag, DockerHelper dockerHelper, bool allowPull = false)
+ {
+ string imageName = GetImageName(tag);
- return imageName;
+ if (IsPublished)
+ {
+ PullImageIfNecessary(imageName, dockerHelper, allowPull);
}
- public string GetTagNameBase(SampleImageType imageType) =>
- Enum.GetName(typeof(SampleImageType), imageType).ToLowerInvariant();
-
- public static string GetImageName(string tag) =>
- GetImageName(tag, "samples", string.Empty);
+ return imageName;
}
+
+ public static string GetTagNameBase(SampleImageType imageType) =>
+ Enum.GetName(imageType).ToLowerInvariant();
+
+ public static string GetImageName(string tag) =>
+ GetImageName(tag, "samples", string.Empty);
}
diff --git a/tests/Microsoft.DotNet.Docker.Tests/SampleImageTests.cs b/tests/Microsoft.DotNet.Docker.Tests/SampleImageTests.cs
index ee2c6b3e5e..7b3f00aed9 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/SampleImageTests.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/SampleImageTests.cs
@@ -1,198 +1,190 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
+using Microsoft.DotNet.Docker.Tests.TestScenarios;
using Xunit;
using Xunit.Abstractions;
-namespace Microsoft.DotNet.Docker.Tests
+namespace Microsoft.DotNet.Docker.Tests;
+
+[Trait("Category", "sample")]
+public class SampleImageTests(ITestOutputHelper outputHelper)
{
- [Trait("Category", "sample")]
- public class SampleImageTests
- {
- private static readonly string s_samplesPath = Path.Combine(Config.SourceRepoRoot, "samples");
+ private static readonly string SamplesPath = Path.Combine(Config.SourceRepoRoot, "samples");
- public SampleImageTests(ITestOutputHelper outputHelper)
- {
- OutputHelper = outputHelper;
- DockerHelper = new DockerHelper(outputHelper);
- }
+ protected DockerHelper DockerHelper { get; } = new DockerHelper(outputHelper);
- protected DockerHelper DockerHelper { get; }
+ protected ITestOutputHelper OutputHelper { get; } = outputHelper;
- protected ITestOutputHelper OutputHelper { get; }
+ public static IEnumerable GetImageData()
+ {
+ return TestData.GetSampleImageData()
+ .Select(imageData => new object[] { imageData });
+ }
- public static IEnumerable GetImageData()
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public async Task VerifyDotnetSample(SampleImageData imageData)
+ {
+ if (imageData.DockerfileSuffix == "windowsservercore-iis")
{
- return TestData.GetSampleImageData()
- .Select(imageData => new object[] { imageData });
+ return;
}
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public async Task VerifyDotnetSample(SampleImageData imageData)
+ await VerifySampleAsync(imageData, SampleImageType.Dotnetapp, (image, containerName) =>
{
- if (imageData.DockerfileSuffix == "windowsservercore-iis")
- {
- return;
- }
-
- await VerifySampleAsync(imageData, SampleImageType.Dotnetapp, (image, containerName) =>
- {
- string output = DockerHelper.Run(image, containerName);
- Assert.True(output.Contains("42") || output.StartsWith("Hello"));
+ string output = DockerHelper.Run(image, containerName);
+ Assert.True(output.Contains("42") || output.StartsWith("Hello", StringComparison.Ordinal));
- ValidateEnvironmentVariables(imageData, image, SampleImageType.Dotnetapp);
+ ValidateEnvironmentVariables(imageData, image, SampleImageType.Dotnetapp);
- return Task.CompletedTask;
- });
- }
+ return Task.CompletedTask;
+ });
+ }
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public async Task VerifyAspnetSample(SampleImageData imageData)
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public async Task VerifyAspnetSample(SampleImageData imageData)
+ {
+ await VerifySampleAsync(imageData, SampleImageType.Aspnetapp, async (image, containerName) =>
{
- await VerifySampleAsync(imageData, SampleImageType.Aspnetapp, async (image, containerName) =>
- {
- int port = imageData.DockerfileSuffix == "windowsservercore-iis" ? 80 : imageData.DefaultPort;
+ int port = imageData.DockerfileSuffix == "windowsservercore-iis" ? 80 : imageData.DefaultPort;
- try
- {
- DockerHelper.Run(
- image: image,
- name: containerName,
- detach: true,
- optionalRunArgs: $"-p {port}",
- skipAutoCleanup: true);
-
- if (!Config.IsHttpVerificationDisabled)
- {
- await WebScenario.VerifyHttpResponseFromContainerAsync(containerName, DockerHelper, OutputHelper, port);
- }
-
- ValidateEnvironmentVariables(imageData, image, SampleImageType.Aspnetapp);
- }
- finally
+ try
+ {
+ DockerHelper.Run(
+ image: image,
+ name: containerName,
+ detach: true,
+ optionalRunArgs: $"-p {port}",
+ skipAutoCleanup: true);
+
+ if (!Config.IsHttpVerificationDisabled)
{
- DockerHelper.DeleteContainer(containerName);
+ await WebScenario.VerifyHttpResponseFromContainerAsync(containerName, DockerHelper, OutputHelper, port);
}
- });
- }
- [Fact]
- public void VerifyMultiProjectAppSample()
- {
- // MultiProjectApp sample doesn't currently support building on Windows.
- if (!DockerHelper.IsLinuxContainerModeEnabled)
+ ValidateEnvironmentVariables(imageData, image, SampleImageType.Aspnetapp);
+ }
+ finally
{
- return;
+ DockerHelper.DeleteContainer(containerName);
}
+ });
+ }
- string appTag = SampleImageData.GetImageName("complexapp-local-app");
- string testTag = SampleImageData.GetImageName("complexapp-local-test");
- string sampleFolder = Path.Combine(s_samplesPath, "MultiProjectApp");
- string dockerfilePath = $"{sampleFolder}/Dockerfile";
- string testContainerName = ImageData.GenerateContainerName("sample-complex-test");
- string tempDir = null;
- try
+ [Fact]
+ public void VerifyMultiProjectAppSample()
+ {
+ // MultiProjectApp sample doesn't currently support building on Windows.
+ if (!DockerHelper.IsLinuxContainerModeEnabled)
+ {
+ return;
+ }
+
+ string appTag = SampleImageData.GetImageName("complexapp-local-app");
+ string testTag = SampleImageData.GetImageName("complexapp-local-test");
+ string sampleFolder = Path.Combine(SamplesPath, "MultiProjectApp");
+ string dockerfilePath = $"{sampleFolder}/Dockerfile";
+ string testContainerName = ImageData.GenerateContainerName("sample-complex-test");
+ string tempDir = null;
+ try
+ {
+ // Test that the app works
+ DockerHelper.Build(appTag, dockerfilePath, contextDir: sampleFolder, pull: Config.PullImages);
+ string containerName = ImageData.GenerateContainerName("sample-complex");
+ string output = DockerHelper.Run(appTag, containerName);
+ Assert.StartsWith("string: The quick brown fox jumps over the lazy dog", output);
+
+ // Run the app's tests
+ DockerHelper.Build(testTag, dockerfilePath, target: "test", contextDir: sampleFolder);
+ DockerHelper.Run(testTag, testContainerName, skipAutoCleanup: true);
+
+ // Copy the test log from the container to the host
+ tempDir = Directory.CreateDirectory(
+ Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())).FullName;
+ DockerHelper.Copy($"{testContainerName}:/source/Tests/TestResults", tempDir);
+ string testLogFile = new DirectoryInfo($"{tempDir}/TestResults").GetFiles("*.trx").First().FullName;
+
+ // Open the test log file and verify the tests passed
+ XDocument doc = XDocument.Load(testLogFile);
+ XElement summary = doc.Root.Element(XName.Get("ResultSummary", doc.Root.Name.NamespaceName));
+ Assert.Equal("Completed", summary.Attribute("outcome").Value);
+ XElement counters = summary.Element(XName.Get("Counters", doc.Root.Name.NamespaceName));
+ Assert.Equal("3", counters.Attribute("total").Value);
+ Assert.Equal("3", counters.Attribute("passed").Value);
+ }
+ finally
+ {
+ if (tempDir != null)
{
- // Test that the app works
- DockerHelper.Build(appTag, dockerfilePath, contextDir: sampleFolder, pull: Config.PullImages);
- string containerName = ImageData.GenerateContainerName("sample-complex");
- string output = DockerHelper.Run(appTag, containerName);
- Assert.StartsWith("string: The quick brown fox jumps over the lazy dog", output);
-
- // Run the app's tests
- DockerHelper.Build(testTag, dockerfilePath, target: "test", contextDir: sampleFolder);
- DockerHelper.Run(testTag, testContainerName, skipAutoCleanup: true);
-
- // Copy the test log from the container to the host
- tempDir = Directory.CreateDirectory(
- Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())).FullName;
- DockerHelper.Copy($"{testContainerName}:/source/Tests/TestResults", tempDir);
- string testLogFile = new DirectoryInfo($"{tempDir}/TestResults").GetFiles("*.trx").First().FullName;
-
- // Open the test log file and verify the tests passed
- XDocument doc = XDocument.Load(testLogFile);
- XElement summary = doc.Root.Element(XName.Get("ResultSummary", doc.Root.Name.NamespaceName));
- Assert.Equal("Completed", summary.Attribute("outcome").Value);
- XElement counters = summary.Element(XName.Get("Counters", doc.Root.Name.NamespaceName));
- Assert.Equal("3", counters.Attribute("total").Value);
- Assert.Equal("3", counters.Attribute("passed").Value);
+ Directory.Delete(tempDir, true);
}
- finally
- {
- if (tempDir != null)
- {
- Directory.Delete(tempDir, true);
- }
- DockerHelper.DeleteContainer(testContainerName);
- DockerHelper.DeleteImage(testTag);
- DockerHelper.DeleteImage(appTag);
- }
+ DockerHelper.DeleteContainer(testContainerName);
+ DockerHelper.DeleteImage(testTag);
+ DockerHelper.DeleteImage(appTag);
}
+ }
- private async Task VerifySampleAsync(
- SampleImageData imageData,
- SampleImageType sampleImageType,
- Func verifyImageAsync)
+ private async Task VerifySampleAsync(
+ SampleImageData imageData,
+ SampleImageType sampleImageType,
+ Func verifyImageAsync)
+ {
+ string image = imageData.GetImage(sampleImageType, DockerHelper);
+ string imageType = Enum.GetName(sampleImageType).ToLowerInvariant();
+ try
{
- string image = imageData.GetImage(sampleImageType, DockerHelper);
- string imageType = Enum.GetName(typeof(SampleImageType), sampleImageType).ToLowerInvariant();
- try
+ if (!imageData.IsPublished)
{
- if (!imageData.IsPublished)
+ string sampleName = sampleImageType switch
{
- string sampleName = sampleImageType switch
- {
- SampleImageType.Dotnetapp => "ConsoleApp",
- SampleImageType.Aspnetapp => "AspNetCoreRazorApp",
- _ => throw new ArgumentOutOfRangeException(nameof(sampleImageType))
- };
- string sampleFolder = Path.Combine(s_samplesPath, sampleName);
- string dockerfilePath = $"{sampleFolder}/Dockerfile";
- if (!string.IsNullOrEmpty(imageData.DockerfileSuffix))
- {
- dockerfilePath += $".{imageData.DockerfileSuffix}";
- }
-
- DockerHelper.Build(image, dockerfilePath, contextDir: sampleFolder, pull: Config.PullImages, platform: imageData.Platform);
- }
-
- string containerName = imageData.GetIdentifier($"sample-{imageType}");
- await verifyImageAsync(image, containerName);
- }
- finally
- {
- if (!imageData.IsPublished)
+ SampleImageType.Dotnetapp => "ConsoleApp",
+ SampleImageType.Aspnetapp => "AspNetCoreRazorApp",
+ _ => throw new ArgumentOutOfRangeException(nameof(sampleImageType))
+ };
+ string sampleFolder = Path.Combine(SamplesPath, sampleName);
+ string dockerfilePath = $"{sampleFolder}/Dockerfile";
+ if (!string.IsNullOrEmpty(imageData.DockerfileSuffix))
{
- DockerHelper.DeleteImage(image);
+ dockerfilePath += $".{imageData.DockerfileSuffix}";
}
+
+ DockerHelper.Build(image, dockerfilePath, contextDir: sampleFolder, pull: Config.PullImages, platform: imageData.Platform);
}
- }
- private void ValidateEnvironmentVariables(SampleImageData imageData, string image, SampleImageType imageType)
+ string containerName = imageData.GetIdentifier($"sample-{imageType}");
+ await verifyImageAsync(image, containerName);
+ }
+ finally
{
- List variables = new List();
- variables.AddRange(ProductImageTests.GetCommonEnvironmentVariables());
-
- if (imageType == SampleImageType.Aspnetapp)
+ if (!imageData.IsPublished)
{
- variables.Add(EnvironmentVariableInfo.Require("ASPNETCORE_HTTP_PORTS", imageData.DefaultPort.ToString()));
+ DockerHelper.DeleteImage(image);
}
+ }
+ }
+
+ private void ValidateEnvironmentVariables(SampleImageData imageData, string image, SampleImageType imageType)
+ {
+ List variables = [.. ProductImageTests.GetCommonEnvironmentVariables()];
- EnvironmentVariableInfo.Validate(
- variables,
- image,
- imageData,
- DockerHelper);
+ if (imageType == SampleImageType.Aspnetapp)
+ {
+ variables.Add(EnvironmentVariableInfo.Require("ASPNETCORE_HTTP_PORTS", imageData.DefaultPort.ToString(CultureInfo.InvariantCulture)));
}
+
+ EnvironmentVariableInfo.Validate(
+ variables,
+ image,
+ DockerHelper);
}
}
diff --git a/tests/Microsoft.DotNet.Docker.Tests/SampleImageType.cs b/tests/Microsoft.DotNet.Docker.Tests/SampleImageType.cs
index a2f4278ad1..52399cd905 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/SampleImageType.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/SampleImageType.cs
@@ -1,12 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-namespace Microsoft.DotNet.Docker.Tests
+namespace Microsoft.DotNet.Docker.Tests;
+
+public enum SampleImageType
{
- public enum SampleImageType
- {
- Dotnetapp,
- Aspnetapp
- }
+ Dotnetapp,
+ Aspnetapp
}
diff --git a/tests/Microsoft.DotNet.Docker.Tests/SdkImageTests.cs b/tests/Microsoft.DotNet.Docker.Tests/SdkImageTests.cs
index 177a9fa7f4..802cbfd222 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/SdkImageTests.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/SdkImageTests.cs
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
+using System.Formats.Tar;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -14,429 +14,404 @@
using System.Threading.Tasks;
using Polly;
using Polly.Retry;
-using System.Formats.Tar;
-using System.IO.Compression;
using Xunit;
using Xunit.Abstractions;
#nullable enable
-namespace Microsoft.DotNet.Docker.Tests
+using Microsoft.DotNet.Docker.Tests.TestScenarios;
+
+namespace Microsoft.DotNet.Docker.Tests;
+
+[Trait("Category", "sdk")]
+public class SdkImageTests(ITestOutputHelper outputHelper) : ProductImageTests(outputHelper)
{
- [Trait("Category", "sdk")]
- public class SdkImageTests : ProductImageTests
- {
- private static readonly Dictionary> s_sdkContentsCache =
- new Dictionary>();
+ private static readonly Dictionary> SdkContentsCache =
+ [];
- private static readonly RetryStrategyOptions s_sdkDownloadRetryStrategy =
- new()
- {
- BackoffType = DelayBackoffType.Exponential,
- MaxRetryAttempts = 4,
- Delay = TimeSpan.FromSeconds(3),
- };
+ private static readonly RetryStrategyOptions SdkDownloadRetryStrategy =
+ new()
+ {
+ BackoffType = DelayBackoffType.Exponential,
+ MaxRetryAttempts = 4,
+ Delay = TimeSpan.FromSeconds(3),
+ };
- private static readonly ResiliencePipeline s_sdkDownloadPipeline =
- new ResiliencePipelineBuilder()
- .AddRetry(s_sdkDownloadRetryStrategy)
- .Build();
+ private static readonly ResiliencePipeline SdkDownloadPipeline =
+ new ResiliencePipelineBuilder()
+ .AddRetry(SdkDownloadRetryStrategy)
+ .Build();
- private static readonly HttpClient s_httpClient = CreateHttpClient();
+ private static readonly HttpClient HttpClient = CreateHttpClient();
- public SdkImageTests(ITestOutputHelper outputHelper)
- : base(outputHelper)
- {
- }
+ protected override DotNetImageRepo ImageRepo => DotNetImageRepo.SDK;
+
+ public static IEnumerable GetImageData()
+ {
+ return TestData.GetImageData(DotNetImageRepo.SDK)
+ .Where(imageData => !imageData.IsDistroless)
+ .Select(imageData => new object[] { imageData });
+ }
- protected override DotNetImageRepo ImageRepo => DotNetImageRepo.SDK;
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public async void VerifyBlazorWasmScenario(ProductImageData imageData)
+ {
+ bool useWasmTools = true;
- public static IEnumerable GetImageData()
+ // `wasm-tools` workload does not work on ARM
+ if (imageData.IsArm)
{
- return TestData.GetImageData(DotNetImageRepo.SDK)
- .Where(imageData => !imageData.IsDistroless)
- .Select(imageData => new object[] { imageData });
+ useWasmTools = false;
}
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public async void VerifyBlazorWasmScenario(ProductImageData imageData)
+ // `wasm-tools` is not supported on Alpine for .NET < 9 due to https://github.com/dotnet/sdk/issues/32327
+ if (imageData.OS.Family == OSFamily.Alpine && imageData.Version.Major == 8)
{
- bool useWasmTools = true;
+ useWasmTools = false;
+ }
- // `wasm-tools` workload does not work on ARM
- if (imageData.IsArm)
- {
- useWasmTools = false;
- }
+ // Emscripten SDK workload is the wrong version on linux-musl-x64 in .NET 10 Preview 6
+ // Remove in nightly branch when https://github.com/dotnet/dotnet/issues/1487 is resolved
+ if (imageData.Version.Major == 10)
+ {
+ useWasmTools = false;
+ }
- // `wasm-tools` is not supported on Alpine for .NET < 9 due to https://github.com/dotnet/sdk/issues/32327
- if (imageData.OS.Family == OSFamily.Alpine && imageData.Version.Major == 8)
- {
- useWasmTools = false;
- }
+ BlazorWasmScenario testScenario = new(imageData, DockerHelper, OutputHelper, useWasmTools);
+ await testScenario.ExecuteAsync();
+ }
- // Emscripten SDK workload is the wrong version on linux-musl-x64 in .NET 10 Preview 6
- // Remove in nightly branch when https://github.com/dotnet/dotnet/issues/1487 is resolved
- if (imageData.Version.Major == 10)
- {
- useWasmTools = false;
- }
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyInsecureFiles(ProductImageData imageData) => VerifyCommonInsecureFiles(imageData);
- BlazorWasmScenario testScenario = new(imageData, DockerHelper, OutputHelper, useWasmTools);
- await testScenario.ExecuteAsync();
- }
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyNoSasToken(ProductImageData imageData) => VerifyCommonNoSasToken(imageData);
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyInsecureFiles(ProductImageData imageData)
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyEnvironmentVariables(ProductImageData imageData)
+ {
+ string imageName = imageData.GetImage(ImageRepo, DockerHelper);
+ string version = imageData.GetProductVersion(ImageRepo, ImageRepo, DockerHelper);
+
+ List variables =
+ [
+ EnvironmentVariableInfo.Require("DOTNET_GENERATE_ASPNET_CERTIFICATE", "false"),
+ EnvironmentVariableInfo.Require("DOTNET_USE_POLLING_FILE_WATCHER", "true"),
+ EnvironmentVariableInfo.Require("NUGET_XMLDOC_MODE", "skip"),
+ EnvironmentVariableInfo.Require("DOTNET_SDK_VERSION", version) with
+ {
+ IsProductVersion = true
+ },
+ AspnetImageTests.GetAspnetVersionVariableInfo(ImageRepo, imageData, DockerHelper),
+ RuntimeImageTests.GetRuntimeVersionVariableInfo(ImageRepo, imageData, DockerHelper),
+ EnvironmentVariableInfo.Require("DOTNET_NOLOGO", "true"),
+ // DOTNET_ROLL_FORWARD must not be set globally, as it can silently change
+ // the runtime that unrelated workloads (e.g. dotnet test) execute on.
+ // See https://github.com/dotnet/dotnet-docker/issues/7255.
+ EnvironmentVariableInfo.Forbid("DOTNET_ROLL_FORWARD"),
+ ..GetCommonEnvironmentVariables(),
+ ];
+
+ if (imageData.SdkOS.Family == OSFamily.Alpine)
{
- base.VerifyCommonInsecureFiles(imageData);
+ variables.Add(EnvironmentVariableInfo.Require("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "false"));
}
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyNoSasToken(ProductImageData imageData)
+ EnvironmentVariableInfo.Validate(variables, imageName, DockerHelper);
+ }
+
+ ///
+ /// Verifies that the dotnet folder contents of an SDK container match the contents in the official SDK archive file.
+ ///
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public async Task VerifyDotnetFolderContents(ProductImageData imageData)
+ {
+ if (Config.IsInternal)
{
- base.VerifyCommonNoSasToken(imageData);
+ // Skip this test for internal builds, since this test does not
+ // yet authenticate to download the internal staged version of
+ // the .NET SDK.
+ return;
}
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyEnvironmentVariables(ProductImageData imageData)
+ // Workaround for https://github.com/dotnet/sdk/issues/55238
+ if (imageData.SdkOS.Family == OSFamily.Alpine && imageData.Version.Major == 11)
{
- string imageName = imageData.GetImage(ImageRepo, DockerHelper);
- string version = imageData.GetProductVersion(ImageRepo, ImageRepo, DockerHelper);
+ return;
+ }
- List variables =
- [
- EnvironmentVariableInfo.Require("DOTNET_GENERATE_ASPNET_CERTIFICATE", "false"),
- EnvironmentVariableInfo.Require("DOTNET_USE_POLLING_FILE_WATCHER", "true"),
- EnvironmentVariableInfo.Require("NUGET_XMLDOC_MODE", "skip"),
- EnvironmentVariableInfo.Require("DOTNET_SDK_VERSION", version) with
- {
- IsProductVersion = true
- },
- AspnetImageTests.GetAspnetVersionVariableInfo(ImageRepo, imageData, DockerHelper),
- RuntimeImageTests.GetRuntimeVersionVariableInfo(ImageRepo, imageData, DockerHelper),
- EnvironmentVariableInfo.Require("DOTNET_NOLOGO", "true"),
- // DOTNET_ROLL_FORWARD must not be set globally, as it can silently change
- // the runtime that unrelated workloads (e.g. dotnet test) execute on.
- // See https://github.com/dotnet/dotnet-docker/issues/7255.
- EnvironmentVariableInfo.Forbid("DOTNET_ROLL_FORWARD"),
- ..GetCommonEnvironmentVariables(),
- ];
-
- if (imageData.SdkOS.Family == OSFamily.Alpine)
- {
- variables.Add(EnvironmentVariableInfo.Require("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "false"));
- }
+ IEnumerable actualDotnetFiles = GetActualSdkContents(imageData);
+ IEnumerable expectedDotnetFiles = await GetExpectedSdkContentsAsync(imageData);
- EnvironmentVariableInfo.Validate(variables, imageName, imageData, DockerHelper);
- }
+ using TempFileContext actualFilesContext = FileHelper.UseTempFile("container-image");
+ using TempFileContext expectedFilesContext = FileHelper.UseTempFile("sdk-archive");
- ///
- /// Verifies that the dotnet folder contents of an SDK container match the contents in the official SDK archive file.
- ///
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public async Task VerifyDotnetFolderContents(ProductImageData imageData)
- {
- if (Config.IsInternal)
- {
- // Skip this test for internal builds, since this test does not
- // yet authenticate to download the internal staged version of
- // the .NET SDK.
- return;
- }
+ File.WriteAllLines(actualFilesContext.Path, actualDotnetFiles.Select(file => $"{file.Path} {file.Sha512}"));
+ File.WriteAllLines(expectedFilesContext.Path, expectedDotnetFiles.Select(file => $"{file.Path} {file.Sha512}"));
- // Workaround for https://github.com/dotnet/sdk/issues/55238
- if (imageData.SdkOS.Family == OSFamily.Alpine && imageData.Version.Major == 11)
- {
- return;
- }
+ bool filesMatch = FileHelper.CompareFiles(expectedFilesContext.Path, actualFilesContext.Path, OutputHelper);
- IEnumerable actualDotnetFiles = GetActualSdkContents(imageData);
- IEnumerable expectedDotnetFiles = await GetExpectedSdkContentsAsync(imageData);
+ Assert.True(filesMatch, "Differences found in the dotnet folder contents.");
+ }
- using TempFileContext actualFilesContext = FileHelper.UseTempFile("container-image");
- using TempFileContext expectedFilesContext = FileHelper.UseTempFile("sdk-archive");
+ [LinuxImageTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyInstalledPackages(ProductImageData imageData) => VerifyInstalledPackagesBase(imageData, ImageRepo);
- File.WriteAllLines(actualFilesContext.Path, actualDotnetFiles.Select(file => $"{file.Path} {file.Sha512}"));
- File.WriteAllLines(expectedFilesContext.Path, expectedDotnetFiles.Select(file => $"{file.Path} {file.Sha512}"));
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyDefaultUser(ProductImageData imageData) => VerifyCommonDefaultUser(imageData);
- bool filesMatch = FileHelper.CompareFiles(expectedFilesContext.Path, actualFilesContext.Path, OutputHelper);
+ ///
+ /// Verifies that a git command can be executed without failure.
+ ///
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyGitInstallation(ProductImageData imageData)
+ {
+ DockerHelper.Run(
+ image: imageData.GetImage(DotNetImageRepo.SDK, DockerHelper),
+ name: imageData.GetIdentifier($"git"),
+ command: "git version");
+ }
- Assert.True(filesMatch, "Differences found in the dotnet folder contents.");
- }
+ ///
+ /// Verifies that a tar command can be executed without failure.
+ ///
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyTarInstallation(ProductImageData imageData)
+ {
+ // tar should exist in the SDK for both Linux and Windows. The --version option works in either OS
+ DockerHelper.Run(
+ image: imageData.GetImage(DotNetImageRepo.SDK, DockerHelper),
+ name: imageData.GetIdentifier("tar"),
+ command: "tar --version"
+ );
+ }
- [LinuxImageTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyInstalledPackages(ProductImageData imageData)
+ ///
+ /// Verifies that dnx is on the PATH and that it is functional.
+ ///
+ [DotNetTheory]
+ [MemberData(nameof(GetImageData))]
+ public void VerifyDnxInstallation(ProductImageData imageData)
+ {
+ if (!imageData.SupportsDnx)
{
- VerifyInstalledPackagesBase(imageData, ImageRepo);
+ return;
}
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyDefaultUser(ProductImageData imageData)
- {
- VerifyCommonDefaultUser(imageData);
- }
+ string dnxCommand = DockerHelper.IsLinuxContainerModeEnabled
+ ? "dnx --help"
+ : "dnx.cmd --help";
+
+ DockerHelper.Run(
+ image: imageData.GetImage(DotNetImageRepo.SDK, DockerHelper),
+ name: imageData.GetIdentifier("dnx"),
+ command: dnxCommand
+ );
+ }
- ///
- /// Verifies that a git command can be executed without failure.
- ///
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyGitInstallation(ProductImageData imageData)
+ private IEnumerable GetActualSdkContents(ProductImageData imageData)
+ {
+ string dotnetPath;
+ string destinationPath;
+ string command;
+
+ if (DockerHelper.IsLinuxContainerModeEnabled)
{
- DockerHelper.Run(
- image: imageData.GetImage(DotNetImageRepo.SDK, DockerHelper),
- name: imageData.GetIdentifier($"git"),
- command: "git version");
- }
+ dotnetPath = "/usr/share/dotnet";
+ destinationPath = "/sdk";
- ///
- /// Verifies that a tar command can be executed without failure.
- ///
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyTarInstallation(ProductImageData imageData)
+ // Alpine's BusyBox find doesn't support comma-separated types (-type f,l)
+ command = imageData.SdkOS.Family == OSFamily.Alpine
+ ? $"find {destinationPath} ( -type f -o -type l ) -exec sha512sum {{}} +"
+ : $"find {destinationPath} -type f,l -exec sha512sum {{}} +";
+ }
+ else
{
- // tar should exist in the SDK for both Linux and Windows. The --version option works in either OS
- DockerHelper.Run(
- image: imageData.GetImage(DotNetImageRepo.SDK, DockerHelper),
- name: imageData.GetIdentifier("tar"),
- command: "tar --version"
- );
+ dotnetPath = "\"Program Files\\dotnet\"";
+ destinationPath = "C:\\sdk";
+ string powerShellCommand =
+ $"Get-ChildItem -File -Force -Recurse '{destinationPath}' " +
+ "| Get-FileHash -Algorithm SHA512 " +
+ "| select @{name='Value'; expression={$_.Hash + ' ' +$_.Path}} " +
+ "| select -ExpandProperty Value";
+ command = $"pwsh -Command \"{powerShellCommand}\"";
}
- ///
- /// Verifies that dnx is on the PATH and that it is functional.
- ///
- [DotNetTheory]
- [MemberData(nameof(GetImageData))]
- public void VerifyDnxInstallation(ProductImageData imageData)
- {
- if (!imageData.SupportsDnx)
- {
- return;
- }
+ string baseImage = imageData.GetImage(ImageRepo, DockerHelper);
+ string tag = imageData.GetIdentifier("SdkContents").ToLowerInvariant();
- var dnxCommand = DockerHelper.IsLinuxContainerModeEnabled
- ? "dnx --help"
- : "dnx.cmd --help";
+ DockerHelper.Build(
+ tag: tag,
+ dockerfile: Path.Combine(DockerHelper.TestArtifactsDir, "Dockerfile.copy"),
+ contextDir: DockerHelper.TestArtifactsDir,
+ platform: imageData.Platform,
+ buildArgs:
+ [
+ $"copy_image={baseImage}",
+ $"base_image={baseImage}",
+ $"copy_origin={dotnetPath}",
+ $"copy_destination={destinationPath}"
+ ]);
+
+ string containerFileList = DockerHelper.Run(
+ image: tag,
+ name: tag,
+ command: command,
+ silenceOutput: true);
+
+ return containerFileList
+ .Split("\n")
+ .Select(line =>
+ {
+ string[] parts = line.Split(" ").Select(part => part.Trim()).ToArray();
+ return new SdkContentFileInfo(Path.GetRelativePath(destinationPath, parts[1]), parts[0]);
+ })
+ .OrderBy(fileInfo => fileInfo.Path);
+ }
- DockerHelper.Run(
- image: imageData.GetImage(DotNetImageRepo.SDK, DockerHelper),
- name: imageData.GetIdentifier("dnx"),
- command: dnxCommand
- );
- }
+ private async Task> GetExpectedSdkContentsAsync(ProductImageData imageData)
+ {
+ string sdkUrl = GetSdkUrl(imageData);
+ OutputHelper.WriteLine("Downloading SDK archive: " + sdkUrl);
- private IEnumerable GetActualSdkContents(ProductImageData imageData)
+ if (!SdkContentsCache.TryGetValue(sdkUrl, out IEnumerable? files))
{
- string dotnetPath;
- string destinationPath;
- string command;
+ using TempFolderContext tempFolder = FileHelper.UseTempFolder();
+ string sdkFile = Path.Combine(tempFolder.Path, "sdk-archive");
+
+ await SdkDownloadPipeline.ExecuteAsync(async cancellationToken => await HttpClient.DownloadFileAsync(new Uri(sdkUrl), sdkFile));
+
+ using TempFolderContext extractFolder = FileHelper.UseTempFolder();
- if (DockerHelper.IsLinuxContainerModeEnabled)
+ if (Path.GetExtension(sdkUrl).Equals(".zip", StringComparison.OrdinalIgnoreCase))
{
- dotnetPath = "/usr/share/dotnet";
- destinationPath = "/sdk";
-
- // Alpine's BusyBox find doesn't support comma-separated types (-type f,l)
- if (imageData.SdkOS.Family == OSFamily.Alpine)
- {
- command = $"find {destinationPath} ( -type f -o -type l ) -exec sha512sum {{}} +";
- }
- else
- {
- command = $"find {destinationPath} -type f,l -exec sha512sum {{}} +";
- }
+ ZipFile.ExtractToDirectory(sdkFile, extractFolder.Path);
}
else
{
- dotnetPath = "\"Program Files\\dotnet\"";
- destinationPath = "C:\\sdk";
- string powerShellCommand =
- $"Get-ChildItem -File -Force -Recurse '{destinationPath}' " +
- "| Get-FileHash -Algorithm SHA512 " +
- "| select @{name='Value'; expression={$_.Hash + ' ' +$_.Path}} " +
- "| select -ExpandProperty Value";
- command = $"pwsh -Command \"{powerShellCommand}\"";
+ using FileStream fileStream = File.OpenRead(sdkFile);
+ using GZipStream gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
+ TarFile.ExtractToDirectory(gzipStream, extractFolder.Path, overwriteFiles: false);
}
- string baseImage = imageData.GetImage(ImageRepo, DockerHelper);
- string tag = imageData.GetIdentifier("SdkContents").ToLower();
-
- DockerHelper.Build(
- tag: tag,
- dockerfile: Path.Combine(DockerHelper.TestArtifactsDir, "Dockerfile.copy"),
- contextDir: DockerHelper.TestArtifactsDir,
- platform: imageData.Platform,
- buildArgs:
- [
- $"copy_image={baseImage}",
- $"base_image={baseImage}",
- $"copy_origin={dotnetPath}",
- $"copy_destination={destinationPath}"
- ]);
-
- string containerFileList = DockerHelper.Run(
- image: tag,
- name: tag,
- command: command,
- silenceOutput: true);
-
- return containerFileList
- .Split("\n")
- .Select(line =>
+ files = Directory.EnumerateFiles(extractFolder.Path, "*", SearchOption.AllDirectories)
+ .Select(file =>
{
- string[] parts = line.Split(" ").Select(part => part.Trim()).ToArray();
- return new SdkContentFileInfo(Path.GetRelativePath(destinationPath, parts[1]), parts[0]);
- })
- .OrderBy(fileInfo => fileInfo.Path);
- }
+ string filePath = Path.GetFullPath(file);
+ string relativePath = Path.GetRelativePath(extractFolder.Path, filePath);
- private async Task> GetExpectedSdkContentsAsync(ProductImageData imageData)
- {
- string sdkUrl = GetSdkUrl(imageData);
- OutputHelper.WriteLine("Downloading SDK archive: " + sdkUrl);
+ byte[] fileData = File.ReadAllBytes(filePath);
+ byte[] sha512HashBytes = SHA512.HashData(fileData);
+ string sha512Hash = Convert.ToHexString(sha512HashBytes);
- if (!s_sdkContentsCache.TryGetValue(sdkUrl, out IEnumerable? files))
- {
- using TempFolderContext tempFolder = FileHelper.UseTempFolder();
- string sdkFile = Path.Combine(tempFolder.Path, "sdk-archive");
+ return new SdkContentFileInfo(relativePath, sha512Hash);
+ })
+ .OrderBy(file => file.Path)
+ .ToArray();
- await s_sdkDownloadPipeline.ExecuteAsync(async cancellationToken =>
- {
- await s_httpClient.DownloadFileAsync(new Uri(sdkUrl), sdkFile);
- });
+ SdkContentsCache.Add(sdkUrl, files);
+ }
- using TempFolderContext extractFolder = FileHelper.UseTempFolder();
+ return files;
+ }
- if (Path.GetExtension(sdkUrl).Equals(".zip", StringComparison.OrdinalIgnoreCase))
- {
- ZipFile.ExtractToDirectory(sdkFile, extractFolder.Path);
- }
- else
- {
- using FileStream fileStream = File.OpenRead(sdkFile);
- using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
- TarFile.ExtractToDirectory(gzipStream, extractFolder.Path, overwriteFiles: false);
- }
-
- files = Directory.EnumerateFiles(extractFolder.Path, "*", SearchOption.AllDirectories)
- .Select(file =>
- {
- string filePath = Path.GetFullPath(file);
- string relativePath = Path.GetRelativePath(extractFolder.Path, filePath);
-
- byte[] fileData = File.ReadAllBytes(filePath);
- byte[] sha512HashBytes = SHA512.HashData(fileData);
- string sha512Hash = Convert.ToHexString(sha512HashBytes);
-
- return new SdkContentFileInfo(relativePath, sha512Hash);
- })
- .OrderBy(file => file.Path)
- .ToArray();
-
- s_sdkContentsCache.Add(sdkUrl, files);
- }
+ private static string GetSdkVersionFileLabel(string sdkBuildVersion, string dotnetVersion)
+ {
+ // This should be kept in sync with the template for computing the SDK version file:
+ // https://github.com/dotnet/dotnet-docker/blob/4f48d36a98187a6e350d54167ef5b568ccd3882f/eng/dockerfile-templates/sdk/Dockerfile.linux.install-sdk#L22-L31
- return files;
- }
+ bool isStableBranding = !sdkBuildVersion.Contains('-')
+ || sdkBuildVersion.Contains("-servicing")
+ || sdkBuildVersion.Contains("-rtm");
- private static string GetSdkVersionFileLabel(string sdkBuildVersion, string dotnetVersion)
+ if (isStableBranding)
{
- // This should be kept in sync with the template for computing the SDK version file:
- // https://github.com/dotnet/dotnet-docker/blob/4f48d36a98187a6e350d54167ef5b568ccd3882f/eng/dockerfile-templates/sdk/Dockerfile.linux.install-sdk#L22-L31
-
- bool isStableBranding = !sdkBuildVersion.Contains('-')
- || sdkBuildVersion.Contains("-servicing")
- || sdkBuildVersion.Contains("-rtm");
+ return Config.GetVariableValue($"sdk|{dotnetVersion}|product-version");
+ }
- if (isStableBranding)
- {
- return Config.GetVariableValue($"sdk|{dotnetVersion}|product-version");
- }
+ bool useFinalVersion =
+ Config.TryGetVariableValue($"sdk|{dotnetVersion}|use-final-version", out string finalFlag)
+ && finalFlag == "true";
+ if (useFinalVersion)
+ {
+ return Config.GetVariableValue($"sdk|{dotnetVersion}|product-version") + ".final";
+ }
- bool useFinalVersion =
- Config.TryGetVariableValue($"sdk|{dotnetVersion}|use-final-version", out string finalFlag)
- && finalFlag == "true";
- if (useFinalVersion)
- {
- return Config.GetVariableValue($"sdk|{dotnetVersion}|product-version") + ".final";
- }
+ return sdkBuildVersion;
+ }
- return sdkBuildVersion;
+ private string GetSdkUrl(ProductImageData imageData)
+ {
+ bool isInternal = Config.IsInternal;
+ string sdkBuildVersion = Config.GetBuildVersion(ImageRepo, imageData.VersionString);
+ string sdkFileVersionLabel = isInternal
+ ? imageData.GetProductVersion(ImageRepo, ImageRepo, DockerHelper)
+ : GetSdkVersionFileLabel(sdkBuildVersion, imageData.VersionString);
+
+ string osType = DockerHelper.IsLinuxContainerModeEnabled ? "linux" : "win";
+ if (imageData.SdkOS.Family == OSFamily.Alpine)
+ {
+ osType += "-musl";
}
- private string GetSdkUrl(ProductImageData imageData)
+ string architecture = imageData.Arch switch
{
- bool isInternal = Config.IsInternal;
- string sdkBuildVersion = Config.GetBuildVersion(ImageRepo, imageData.VersionString);
- string sdkFileVersionLabel = isInternal
- ? imageData.GetProductVersion(ImageRepo, ImageRepo, DockerHelper)
- : GetSdkVersionFileLabel(sdkBuildVersion, imageData.VersionString);
-
- string osType = DockerHelper.IsLinuxContainerModeEnabled ? "linux" : "win";
- if (imageData.SdkOS.Family == OSFamily.Alpine)
- {
- osType += "-musl";
- }
+ Arch.Amd64 => "x64",
+ Arch.Arm => "arm",
+ Arch.Arm64 => "arm64",
+ _ => throw new InvalidOperationException($"Unexpected architecture type: '{imageData.Arch}'"),
+ };
- string architecture = imageData.Arch switch
- {
- Arch.Amd64 => "x64",
- Arch.Arm => "arm",
- Arch.Arm64 => "arm64",
- _ => throw new InvalidOperationException($"Unexpected architecture type: '{imageData.Arch}'"),
- };
+ string fileType = DockerHelper.IsLinuxContainerModeEnabled ? "tar.gz" : "zip";
+ string baseUrl = Config.GetBaseUrl(imageData.VersionString);
+ string url = $"{baseUrl}/Sdk/{sdkBuildVersion}/dotnet-sdk-{sdkFileVersionLabel}-{osType}-{architecture}.{fileType}";
- string fileType = DockerHelper.IsLinuxContainerModeEnabled ? "tar.gz" : "zip";
- string baseUrl = Config.GetBaseUrl(imageData.VersionString);
- string url = $"{baseUrl}/Sdk/{sdkBuildVersion}/dotnet-sdk-{sdkFileVersionLabel}-{osType}-{architecture}.{fileType}";
+ return url;
+ }
- return url;
- }
+ private sealed record SdkContentFileInfo
+ {
+ public string Path { get; init; }
+ public string Sha512 { get; init; }
- private record SdkContentFileInfo
+ public SdkContentFileInfo(string path, string sha512)
{
- public string Path { get; init; }
- public string Sha512 { get; init; }
-
- public SdkContentFileInfo(string path, string sha512)
- {
- Path = NormalizePath(path);
- Sha512 = sha512.ToLower();
- }
-
- private static string NormalizePath(string path)
- {
- return path
- .Replace("\\", "/")
- .Replace("/usr/share/dotnet", string.Empty, StringComparison.OrdinalIgnoreCase)
- .Replace("c:/program files/dotnet", string.Empty, StringComparison.OrdinalIgnoreCase)
- .TrimStart('/')
- .ToLower();
- }
+ Path = NormalizePath(path);
+ Sha512 = sha512.ToLowerInvariant();
}
- private static HttpClient CreateHttpClient()
+ private static string NormalizePath(string path)
{
- var client = new HttpClient();
+ return path
+ .Replace("\\", "/")
+ .Replace("/usr/share/dotnet", string.Empty, StringComparison.OrdinalIgnoreCase)
+ .Replace("c:/program files/dotnet", string.Empty, StringComparison.OrdinalIgnoreCase)
+ .TrimStart('/')
+ .ToLowerInvariant();
+ }
+ }
- if (Config.IsInternal)
- {
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
- "Basic",
- Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "",
- Config.InternalAccessToken))));
- };
+ private static HttpClient CreateHttpClient()
+ {
+ HttpClient client = new HttpClient();
- return client;
+ if (Config.IsInternal)
+ {
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
+ "Basic",
+ Convert.ToBase64String(Encoding.ASCII.GetBytes($":{Config.InternalAccessToken}")));
}
+
+ ;
+
+ return client;
}
}
diff --git a/tests/Microsoft.DotNet.Docker.Tests/SkippableTheoryAttribute.cs b/tests/Microsoft.DotNet.Docker.Tests/SkippableTheoryAttribute.cs
index c8507be87a..d92ab2eb68 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/SkippableTheoryAttribute.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/SkippableTheoryAttribute.cs
@@ -1,29 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System.Linq;
using System.Text.RegularExpressions;
-namespace Microsoft.DotNet.Docker.Tests
+namespace Microsoft.DotNet.Docker.Tests;
+
+public class SkippableTheoryAttribute : DotNetTheoryAttribute
{
- public class SkippableTheoryAttribute : DotNetTheoryAttribute
+ public SkippableTheoryAttribute(params string[] skippedOperatingSystems)
{
- public SkippableTheoryAttribute(params string[] skippedOperatingSystems)
+ if (Config.OsNames.Length != 0 && !Config.OsNames.Any(osName => osName != "*"))
{
- if (Config.OsNames.Any() && !Config.OsNames.Any(osName => osName != "*"))
+ foreach (string osName in Config.OsNames)
{
- foreach (string osName in Config.OsNames)
+ string osPattern =
+ osName != null ? Config.GetFilterRegexPattern(osName) : null;
+ foreach (string skippedOperatingSystem in skippedOperatingSystems)
{
- string osPattern =
- osName != null ? Config.GetFilterRegexPattern(osName) : null;
- foreach (string skippedOperatingSystem in skippedOperatingSystems)
+ if (Regex.IsMatch(skippedOperatingSystem, osPattern, RegexOptions.IgnoreCase))
{
- if (Regex.IsMatch(skippedOperatingSystem, osPattern, RegexOptions.IgnoreCase))
- {
- Skip = $"{skippedOperatingSystem} is unsupported";
- break;
- }
+ Skip = $"{skippedOperatingSystem} is unsupported";
+ break;
}
}
}
diff --git a/tests/Microsoft.DotNet.Docker.Tests/StaticTagTests.cs b/tests/Microsoft.DotNet.Docker.Tests/StaticTagTests.cs
index 2d52902c4b..b73e828353 100644
--- a/tests/Microsoft.DotNet.Docker.Tests/StaticTagTests.cs
+++ b/tests/Microsoft.DotNet.Docker.Tests/StaticTagTests.cs
@@ -1,682 +1,677 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
-using Microsoft.CodeAnalysis;
-using Microsoft.DotNet.Docker.Shared;
using FluentAssertions;
using FluentAssertions.Execution;
+using Microsoft.CodeAnalysis;
+using Microsoft.DotNet.Docker.Shared;
using Xunit;
-
using static Microsoft.DotNet.Docker.Tests.ManifestHelper;
#nullable enable
-namespace Microsoft.DotNet.Docker.Tests
+namespace Microsoft.DotNet.Docker.Tests;
+
+[Trait("Category", "pre-build")]
+public class StaticTagTests
{
- [Trait("Category", "pre-build")]
- public class StaticTagTests
+ public enum VersionType
{
- public enum VersionType
- {
- Major = 1,
- MajorMinor = 2,
- MajorMinorPatch = 3
- }
+ Major = 1,
+ MajorMinor = 2,
+ MajorMinorPatch = 3
+ }
- private const string LatestTagValue = "latest";
- private const string SingleNumberRegex = @"\d+";
- private const string MajorVersionRegex = SingleNumberRegex;
- private const string MajorMinorVersionRegex = @$"{SingleNumberRegex}\.{SingleNumberRegex}";
- private static readonly string[] ApplianceRepoIds =
- [
- "monitor",
- "monitor-base",
- "aspire-dashboard",
- "yarp"
- ];
-
- private enum TestType
- {
- // Tests validating the 'latest' tags.
- // Tests begin with "LatestTag_"
- Latest,
-
- // Tests validating platform-specific tags
- // Tags have variations of versions, oses, and architectures.
- // Does not include tags with only versions specified ().
- // Tests begin with "PlatformTag_"
- Platform,
-
- // Tests validating alpine floating tags
- // Tags have variations of versions and architectures.
- // Tests begin with "FloatingAlpineTag_"
- FloatingAlpine,
-
- // Tests validating version tags
- // Tags have variations of versions
- // Does not include tags with specified oses or architectures ().
- // Tests begin with "VersionTag_"
- Version
- }
+ private const string LatestTagValue = "latest";
+ private const string SingleNumberRegex = @"\d+";
+ private const string MajorVersionRegex = SingleNumberRegex;
+ private const string MajorMinorVersionRegex = @$"{SingleNumberRegex}\.{SingleNumberRegex}";
+ private static readonly string[] ApplianceRepoIds =
+ [
+ "monitor",
+ "monitor-base",
+ "aspire-dashboard",
+ "yarp"
+ ];
+
+ private enum TestType
+ {
+ // Tests validating the 'latest' tags.
+ // Tests begin with "LatestTag_"
+ Latest,
+
+ // Tests validating platform-specific tags
+ // Tags have variations of versions, oses, and architectures.
+ // Does not include tags with only versions specified ().
+ // Tests begin with "PlatformTag_"
+ Platform,
+
+ // Tests validating alpine floating tags
+ // Tags have variations of versions and architectures.
+ // Tests begin with "FloatingAlpineTag_"
+ FloatingAlpine,
+
+ // Tests validating version tags
+ // Tags have variations of versions
+ // Does not include tags with specified oses or architectures ().
+ // Tests begin with "VersionTag_"
+ Version
+ }
- [Theory]
- [MemberData(nameof(GetTagTestObjects), TestType.Latest)]
- public void LatestTag_OnePerRepo(Repo repo)
- {
- IEnumerable latestTagImages = repo.Images
- .Where(image => ManifestHelper.GetResolvedSharedTags(image).Contains(LatestTagValue));
+ [Theory]
+ [MemberData(nameof(GetTagTestObjects), TestType.Latest)]
+ public void LatestTag_OnePerRepo(Repo repo)
+ {
+ IEnumerable latestTagImages = repo.Images
+ .Where(image => GetResolvedSharedTags(image).Contains(LatestTagValue));
- latestTagImages.Should().ContainSingle("expected exactly one image to have the latest tag");
- }
+ latestTagImages.Should().ContainSingle("expected exactly one image to have the latest tag");
+ }
- [Theory]
- [MemberData(nameof(GetTagTestObjects), TestType.Latest)]
- public void LatestTag_IsNotPlatformSpecific(Repo repo)
+ [Theory]
+ [MemberData(nameof(GetTagTestObjects), TestType.Latest)]
+ public void LatestTag_IsNotPlatformSpecific(Repo repo)
+ {
+ foreach (Image image in repo.Images)
{
- foreach (Image image in repo.Images)
- {
- IEnumerable latestTagPlatforms = image.Platforms
- .Where(platform => ManifestHelper.GetResolvedTags(platform).Contains(LatestTagValue));
+ IEnumerable latestTagPlatforms = image.Platforms
+ .Where(platform => ManifestHelper.GetResolvedTags(platform).Contains(LatestTagValue));
- latestTagPlatforms.Should().BeEmpty("expected latest tag to be a shared tag, not a platform-specific tag");
- }
+ latestTagPlatforms.Should().BeEmpty("expected latest tag to be a shared tag, not a platform-specific tag");
}
+ }
- [Theory]
- [MemberData(nameof(GetTagTestObjects), TestType.Latest)]
- public void LatestTag_OnCorrectMajorVersion(Repo repo)
- {
- IEnumerable<(Image Image, List SharedTags)> imageDatas = repo.Images
- .Select(image => (Image: image, SharedTags: ManifestHelper.GetResolvedSharedTags(image)));
+ [Theory]
+ [MemberData(nameof(GetTagTestObjects), TestType.Latest)]
+ public void LatestTag_OnCorrectMajorVersion(Repo repo)
+ {
+ IEnumerable<(Image Image, List SharedTags)> imageDatas = repo.Images
+ .Select(image => (Image: image, SharedTags: GetResolvedSharedTags(image)));
- (Image Image, List SharedTags) latestImageData = imageDatas
- .Where(imageData => imageData.SharedTags.Contains(LatestTagValue))
- .First();
+ (Image Image, List SharedTags) latestImageData = imageDatas
+ .First(imageData => imageData.SharedTags.Contains(LatestTagValue));
- int expectedMajorVersion = GetExpectedLatestMajorVersion(repo);
+ int expectedMajorVersion = GetExpectedLatestMajorVersion(repo);
- latestImageData.SharedTags.Should().ContainMatch($"{expectedMajorVersion}.*",
- because: $"latest tag should be on .NET {expectedMajorVersion} in repo {repo.Name}");
+ latestImageData.SharedTags.Should().ContainMatch($"{expectedMajorVersion}.*",
+ because: $"latest tag should be on .NET {expectedMajorVersion} in repo {repo.Name}");
+ }
+
+ // - -- Non-windows only for non-Appliance repos, old schema only for Appliance repos
+ // - -- Non-windows only for non-Appliance repos, old schema only for Appliance repos
+ // - -- Old schema only for Appliance repos
+ // - - Non-Appliance repos, old schema only for Appliance repos
+ // - - Non-Appliance repos, old schema only for Appliance repos
+ // - -- New schema only for Appliance repos only
+ // - - New schema only for Appliance repos only
+ // - - New schema and non-monitor for Appliance repos only
+ [Theory]
+ [MemberData(nameof(GetTagTestObjects), TestType.Platform)]
+ public void PlatformTag_TagExists(
+ Repo repo,
+ VersionType versionType,
+ bool checkOs,
+ bool checkArchitecture,
+ Func skipDockerFileOn)
+ {
+ if (!checkOs && !checkArchitecture)
+ {
+ throw new ArgumentException("At least one of 'checkOs' or 'checkArchitecture' must be true. " +
+ "Please use the VersionTag tests for this scenario.");
}
- // - -- Non-windows only for non-Appliance repos, old schema only for Appliance repos
- // - -- Non-windows only for non-Appliance repos, old schema only for Appliance repos
- // - -- Old schema only for Appliance repos
- // - - Non-Appliance repos, old schema only for Appliance repos
- // - - Non-Appliance repos, old schema only for Appliance repos
- // - -- New schema only for Appliance repos only
- // - - New schema only for Appliance repos only
- // - - New schema and non-monitor for Appliance repos only
- [Theory]
- [MemberData(nameof(GetTagTestObjects), TestType.Platform)]
- public void PlatformTag_TagExists(
- Repo repo,
- VersionType versionType,
- bool checkOs,
- bool checkArchitecture,
- Func skipDockerFileOn)
+ Dictionary> dockerfileTags = GetDockerfileTags(repo);
+ foreach ((DockerfileInfo dockerfileInfo, List tags) in dockerfileTags)
{
- if (!checkOs && !checkArchitecture)
+ if (skipDockerFileOn(dockerfileInfo))
{
- throw new ArgumentException("At least one of 'checkOs' or 'checkArchitecture' must be true. " +
- "Please use the VersionTag tests for this scenario.");
+ continue;
}
- Dictionary> dockerfileTags = ManifestHelper.GetDockerfileTags(repo);
- foreach ((DockerfileInfo dockerfileInfo, List tags) in dockerfileTags)
+ DotNetVersion dockerfileVersion = dockerfileInfo.ProductVersion;
+
+ bool hasPreviewInMajorVersionGroup = HasPreviewProductVersion(repo, dockerfileVersion.Major);
+
+ // .NET Monitor 8 Azure Linux images do not have Azure Linux platform
+ // (e.g. *-amd64) tags but do have undocumented CBL-Mariner tags. The tag
+ // pattern test assumes that a dockerfile will produce platform tags for the
+ // underlying OS, which is true for most cases, but not for .NET Monitor 8
+ // due to combination of undocumenting platform tags for appliance images
+ // (which only .NET Monitor had at the time) and the update from CBL-Mariner
+ // to Azure Linux. Rewrite the OS to match CBL-Mariner in this instance.
+ string os = dockerfileInfo.OsDir;
+ if (repo.Name.EndsWith("monitor", StringComparison.Ordinal) &&
+ dockerfileInfo.VersionDir.StartsWith("8.", StringComparison.Ordinal) &&
+ string.Equals(OS.AzureLinuxDistroless, os, StringComparison.Ordinal))
{
- if (skipDockerFileOn(dockerfileInfo))
- {
- continue;
- }
+ os = OS.MarinerDistroless;
+ }
- var dockerfileVersion = dockerfileInfo.ProductVersion;
-
- bool hasPreviewInMajorVersionGroup = HasPreviewProductVersion(repo, dockerfileVersion.Major);
-
- // .NET Monitor 8 Azure Linux images do not have Azure Linux platform
- // (e.g. *-amd64) tags but do have undocumented CBL-Mariner tags. The tag
- // pattern test assumes that a dockerfile will produce platform tags for the
- // underlying OS, which is true for most cases, but not for .NET Monitor 8
- // due to combination of undocumenting platform tags for appliance images
- // (which only .NET Monitor had at the time) and the update from CBL-Mariner
- // to Azure Linux. Rewrite the OS to match CBL-Mariner in this instance.
- string os = dockerfileInfo.OsDir;
- if (repo.Name.EndsWith("monitor") &&
- dockerfileInfo.VersionDir.StartsWith("8.") &&
- OS.AzureLinuxDistroless.Equals(os))
- {
- os = OS.MarinerDistroless;
- }
+ Regex expectedPattern = GetTagRegex(
+ versionType,
+ dockerfileInfo.ProductVersion,
+ checkOs ? os : null,
+ checkArchitecture ? dockerfileInfo.ArchitectureDir : null);
- Regex expectedPattern = GetTagRegex(
- versionType,
- dockerfileInfo.ProductVersion,
- checkOs ? os : null,
- checkArchitecture ? dockerfileInfo.ArchitectureDir : null);
+ IEnumerable matchingTags = tags.Where(tag => expectedPattern.IsMatch(tag));
- var matchingTags = tags.Where(tag => expectedPattern.IsMatch(tag));
+ if (versionType == VersionType.Major && !IsLatestInMajorVersionGroup(repo, dockerfileInfo.ProductVersion, hasPreviewInMajorVersionGroup))
+ {
+ // Special case for major version tags
+ // These tags should be on the latest Major.Minor GA version for the respective major version
+ matchingTags.Should().BeEmpty("expected tag to be on latest Major.Minor version for version " +
+ dockerfileVersion.Major + ", but found the tag on " + dockerfileInfo);
+ }
+ else
+ {
+ matchingTags
+ .Should()
+ .ContainSingle($"{dockerfileInfo} requires exactly one tag that matches the expected pattern {expectedPattern}");
+ }
+ }
+ }
- if (versionType == VersionType.Major && !IsLatestInMajorVersionGroup(repo, dockerfileInfo.ProductVersion, hasPreviewInMajorVersionGroup))
+ // - -alpine-
+ // - -alpine
+ // - -alpine
+ // - -alpine-
+ // - -alpine-
+ // - -alpine Appliance repos only
+ [Theory]
+ [MemberData(nameof(GetTagTestObjects), TestType.FloatingAlpine)]
+ public void FloatingAlpineTag_OnLatestVersion(Repo repo, VersionType versionType, bool checkArchitecture)
+ {
+ IEnumerable>> alpineDockerfileTags =
+ GetDockerfileTags(repo)
+ .Where(p => p.Key.OsDir.Contains(OS.Alpine));
+
+ using (new AssertionScope())
+ {
+ foreach ((DockerfileInfo dockerfileInfo, List tags) in alpineDockerfileTags)
+ {
+ string alpineFloatingTagVersion = GetAlpineFloatingTagVersion(dockerfileInfo);
+ Regex pattern = GetFloatingTagRegex(dockerfileInfo);
+ if (dockerfileInfo.OsDir == alpineFloatingTagVersion)
{
- // Special case for major version tags
- // These tags should be on the latest Major.Minor GA version for the respective major version
- matchingTags.Should().BeEmpty("expected tag to be on latest Major.Minor version for version " +
- dockerfileVersion.Major + ", but found the tag on " + dockerfileInfo);
+ tags.Should().ContainSingle(tag => pattern.IsMatch(tag),
+ because: $"image {dockerfileInfo} should have an {OS.Alpine} floating tag");
}
else
{
- matchingTags
- .Should()
- .ContainSingle($"{dockerfileInfo} requires exactly one tag that matches the expected pattern {expectedPattern}");
+ tags.Should().NotContain(tag => pattern.IsMatch(tag),
+ because: $"image {dockerfileInfo} should not have an {OS.Alpine} floating tag");
}
}
}
- // - -alpine-
- // - -alpine
- // - -alpine
- // - -alpine-
- // - -alpine-
- // - -alpine Appliance repos only
- [Theory]
- [MemberData(nameof(GetTagTestObjects), TestType.FloatingAlpine)]
- public void FloatingAlpineTag_OnLatestVersion(Repo repo, VersionType versionType, bool checkArchitecture)
- {
- IEnumerable>> alpineDockerfileTags =
- GetDockerfileTags(repo)
- .Where(p => p.Key.OsDir.Contains(OS.Alpine));
-
- using (new AssertionScope())
- {
- foreach ((DockerfileInfo dockerfileInfo, List tags) in alpineDockerfileTags)
- {
- string alpineFloatingTagVersion = GetAlpineFloatingTagVersion(dockerfileInfo);
- Regex pattern = GetFloatingTagRegex(dockerfileInfo);
- if (dockerfileInfo.OsDir == alpineFloatingTagVersion)
- {
- tags.Should().ContainSingle(tag => pattern.IsMatch(tag),
- because: $"image {dockerfileInfo} should have an {OS.Alpine} floating tag");
- }
- else
- {
- tags.Should().NotContain(tag => pattern.IsMatch(tag),
- because: $"image {dockerfileInfo} should not have an {OS.Alpine} floating tag");
- }
- }
- }
+ string GetAlpineFloatingTagVersion(DockerfileInfo info) =>
+ Config.GetVariableValue($"alpine|{info.VersionDir}|floating-tag-version");
- string GetAlpineFloatingTagVersion(DockerfileInfo info) =>
- Config.GetVariableValue($"alpine|{info.VersionDir}|floating-tag-version");
+ Regex GetFloatingTagRegex(DockerfileInfo info) =>
+ GetTagRegex(
+ versionType,
+ productVersion: info.ProductVersion,
+ os: OS.Alpine,
+ architecture: checkArchitecture ? info.ArchitectureDir : null);
+ }
- Regex GetFloatingTagRegex(DockerfileInfo info) =>
- GetTagRegex(
- versionType,
- productVersion: info.ProductVersion,
- os: OS.Alpine,
- architecture: checkArchitecture ? info.ArchitectureDir : null);
- }
+ // -
+ // -
+ // - Appliance repos only
+ [Theory]
+ [MemberData(nameof(GetTagTestObjects), TestType.Version)]
+ public void VersionTag_SameOsAndVersion(Repo repo, VersionType versionType)
+ {
+ // Group tags -> dockerfiles
+ Dictionary> tagsToDockerfiles =
+ GetDockerfileTags(repo)
+ .SelectMany(pair =>
+ {
+ (DockerfileInfo? dockerfileInfo, List? allTags) = pair;
+ return allTags
+ .Where(tag => IsTagOfFormat(
+ tag: tag,
+ versionType: versionType,
+ productVersion: dockerfileInfo.ProductVersion,
+ os: null,
+ architecture: null))
+ .Select(tag => (tag, pair.Key));
+ })
+ .GroupBy(pair => pair.tag, pair => pair.Key)
+ .ToDictionary(group => group.Key, group => group.ToList());
- // -
- // -
- // - Appliance repos only
- [Theory]
- [MemberData(nameof(GetTagTestObjects), TestType.Version)]
- public void VersionTag_SameOsAndVersion(Repo repo, VersionType versionType)
+ foreach (KeyValuePair> tagToDockerfiles in tagsToDockerfiles)
{
- // Group tags -> dockerfiles
- Dictionary> tagsToDockerfiles =
- GetDockerfileTags(repo)
- .SelectMany(pair =>
- {
- var (dockerfileInfo, allTags) = pair;
- return allTags
- .Where(tag => IsTagOfFormat(
- tag: tag,
- versionType: versionType,
- productVersion: dockerfileInfo.ProductVersion,
- os: null,
- architecture: null))
- .Select(tag => (tag, pair.Key));
- })
- .GroupBy(pair => pair.tag, pair => pair.Key)
- .ToDictionary(group => group.Key, group => group.ToList());
-
- foreach (KeyValuePair> tagToDockerfiles in tagsToDockerfiles)
- {
- string tag = tagToDockerfiles.Key;
- List dockerfiles = tagToDockerfiles.Value;
+ string tag = tagToDockerfiles.Key;
+ List dockerfiles = tagToDockerfiles.Value;
- var dockerfileProductVersions = dockerfiles
- .Select(dockerfile => dockerfile.ProductVersion)
- .Distinct()
- .ToList();
+ List dockerfileProductVersions = dockerfiles
+ .Select(dockerfile => dockerfile.ProductVersion)
+ .Distinct()
+ .ToList();
- dockerfileProductVersions.Should().ContainSingle(
- "all dockerfiles for tag " + tag + " should have the same version");
+ dockerfileProductVersions.Should().ContainSingle(
+ "all dockerfiles for tag " + tag + " should have the same version");
- if (versionType == VersionType.Major)
- {
- var dockerfileVersion = dockerfileProductVersions.First();
+ if (versionType == VersionType.Major)
+ {
+ DotNetVersion dockerfileVersion = dockerfileProductVersions.First();
- // Special case for major version tags
- // These tags should be on the most up-to-date Major.Minor version for the respective major version
- IsLatestInMajorVersionGroup(repo, dockerfileVersion, tag.Contains("-preview")).Should().BeTrue(
- "expected tag to be on the latest Major.Minor GA version for the major version " +
- dockerfileVersion.Major + ", but found the tag on " + dockerfileVersion);
- }
+ // Special case for major version tags
+ // These tags should be on the most up-to-date Major.Minor version for the respective major version
+ IsLatestInMajorVersionGroup(repo, dockerfileVersion, tag.Contains("-preview")).Should().BeTrue(
+ "expected tag to be on the latest Major.Minor GA version for the major version " +
+ dockerfileVersion.Major + ", but found the tag on " + dockerfileVersion);
+ }
- List dockerfileOses = dockerfiles
- .Select(dockerfile => dockerfile.OsDir)
- .Distinct()
- .ToList();
+ List dockerfileOses = dockerfiles
+ .Select(dockerfile => dockerfile.OsDir)
+ .Distinct()
+ .ToList();
- dockerfileOses.Should().ContainSingle(
- "all dockerfiles for tag " + tag + " should have the same os");
- }
+ dockerfileOses.Should().ContainSingle(
+ "all dockerfiles for tag " + tag + " should have the same os");
}
+ }
- private static IEnumerable GetTagTestObjects(TestType testType)
+ private static object[][] GetTagTestObjects(TestType testType)
+ {
+ List testObjects = [];
+ foreach (Repo repo in GetManifest().Repos)
{
- List testObjects = new List();
- foreach (Repo repo in ManifestHelper.GetManifest().Repos)
+ switch (testType)
{
- switch (testType)
- {
- case TestType.Latest:
- testObjects.Add(GetTagTestInput(testType, repo));
- break;
+ case TestType.Latest:
+ testObjects.Add(GetTagTestInput(testType, repo));
+ break;
- case TestType.Platform:
- if (ApplianceRepoIds.Contains(repo.Id))
- {
- // Only appliance repos have major version tags
- // Only appliance repos have floating distro tags ()
- testObjects.AddRange(new[]
- {
- GetTagTestInput(
- testType,
- repo,
- VersionType.MajorMinorPatch,
- checkOs: true,
- checkArchitecture: true,
- skipDockerfileOn: IsApplianceVersionUsingNewSchema
- ), // --
-
- GetTagTestInput(
- testType,
- repo,
- VersionType.MajorMinorPatch,
- checkOs: true,
- checkArchitecture: false,
- skipDockerfileOn: IsApplianceVersionUsingNewSchema
- ), // -
-
- GetTagTestInput(
- testType,
- repo,
- VersionType.MajorMinorPatch,
- checkOs: false,
- checkArchitecture: true,
- skipDockerfileOn: IsApplianceVersionUsingOldSchema
- ), // -
-
- GetTagTestInput(
- testType,
- repo,
- VersionType.MajorMinor,
- checkOs: true,
- checkArchitecture: true,
- skipDockerfileOn: IsApplianceVersionUsingNewSchema
- ), // --
-
- GetTagTestInput(
- testType,
- repo,
- VersionType.MajorMinor,
- checkOs: true,
- checkArchitecture: false,
- skipDockerfileOn: IsApplianceVersionUsingNewSchema
- ), // -
-
- GetTagTestInput(
- testType,
- repo,
- VersionType.MajorMinor,
- checkOs: false,
- checkArchitecture: true,
- skipDockerfileOn: IsApplianceVersionUsingOldSchema
- ), // -
-
- GetTagTestInput(
- testType,
- repo,
- VersionType.Major,
- checkOs: true,
- checkArchitecture: true,
- skipDockerfileOn: IsApplianceVersionUsingNewSchema
- ), // --
-
- GetTagTestInput(
- testType,
- repo,
- VersionType.Major,
- checkOs: true,
- checkArchitecture: false,
- skipDockerfileOn: IsApplianceVersionUsingNewSchema
- ) //