diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 7f020cbe..3d6bb796 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"docfx": {
- "version": "2.75.2",
+ "version": "2.75.3",
"commands": ["docfx"]
}
}
diff --git a/.editorconfig b/.editorconfig
index e207f021..f1a68021 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,401 +1,401 @@
-root = true
-
-# All files
-[*]
-indent_style = space
-max_line_length = 120
-
-# Xml files
-[*.xml]
-indent_size = 2
-
-# C# files
-[*.cs]
-
-#### Core EditorConfig Options ####
-
-# Indentation and spacing
-indent_size = 4
-tab_width = 4
-
-# New line preferences
-end_of_line = crlf
-insert_final_newline = true
-
-#### Custom Analyzer Rules ####
-# Until https://github.com/SonarSource/sonar-dotnet/issues/7624 is shipped.
-dotnet_diagnostic.S3604.severity = none
-
-dotnet_diagnostic.SA0001.severity = none
-dotnet_diagnostic.SA1010.severity = none
-dotnet_diagnostic.SA1600.severity = none
-dotnet_diagnostic.CS1591.severity = none
-dotnet_diagnostic.SA1008.severity = suggestion
-dotnet_diagnostic.SA1009.severity = suggestion
-dotnet_diagnostic.SA1101.severity = none
-dotnet_diagnostic.SA1106.severity = suggestion
-dotnet_diagnostic.SA1118.severity = none
-dotnet_diagnostic.SX1101.severity = suggestion
-dotnet_diagnostic.SA1309.severity = none
-dotnet_diagnostic.SX1309.severity = warning
-dotnet_diagnostic.SA1313.severity = suggestion
-dotnet_diagnostic.SA1501.severity = suggestion
-dotnet_diagnostic.SA1503.severity = suggestion
-dotnet_diagnostic.SA1623.severity = none
-dotnet_diagnostic.SA1633.severity = none
-dotnet_diagnostic.SA1642.severity = none
-dotnet_diagnostic.SA1643.severity = none
-dotnet_diagnostic.SA1649.severity = none
-dotnet_diagnostic.RCS1029.severity = none
-dotnet_diagnostic.RCS1090.severity = none
-
-#### Custom Coding Conventions ####
-resharper_csharp_space_in_singleline_accessorholder = true
-resharper_csharp_space_between_accessors_in_singleline_property = true
-resharper_csharp_space_in_singleline_method = true
-resharper_csharp_space_in_singleline_anonymous_method = true
-resharper_csharp_space_within_single_line_array_initializer_braces = true
-resharper_csharp_trailing_comma_in_multiline_lists = true
-resharper_csharp_place_type_attribute_on_same_line = false
-resharper_csharp_place_attribute_on_same_line = false
-
-#### .NET Coding Conventions ####
-[*.{cs,vb}]
-
-# Organize usings
-dotnet_separate_import_directive_groups = true
-dotnet_sort_system_directives_first = true
-file_header_template = unset
-
-# this. and Me. preferences
-dotnet_style_qualification_for_event = false:silent
-dotnet_style_qualification_for_field = false:silent
-dotnet_style_qualification_for_method = false:silent
-dotnet_style_qualification_for_property = false:silent
-
-# Language keywords vs BCL types preferences
-dotnet_style_predefined_type_for_locals_parameters_members = true:silent
-dotnet_style_predefined_type_for_member_access = true:silent
-
-# Parentheses preferences
-dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
-dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
-dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
-
-# Modifier preferences
-dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
-
-# Expression-level preferences
-dotnet_style_coalesce_expression = true:suggestion
-dotnet_style_collection_initializer = true:suggestion
-dotnet_style_explicit_tuple_names = true:suggestion
-dotnet_style_null_propagation = true:suggestion
-dotnet_style_object_initializer = true:suggestion
-dotnet_style_operator_placement_when_wrapping = beginning_of_line
-dotnet_style_prefer_auto_properties = true:suggestion
-dotnet_style_prefer_compound_assignment = true:suggestion
-dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
-dotnet_style_prefer_conditional_expression_over_return = true:suggestion
-dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
-dotnet_style_prefer_inferred_tuple_names = true:suggestion
-dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
-dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
-dotnet_style_prefer_simplified_interpolation = true:suggestion
-
-# Field preferences
-dotnet_style_readonly_field = true:warning
-
-# Parameter preferences
-dotnet_code_quality_unused_parameters = all:suggestion
-
-# Suppression preferences
-dotnet_remove_unnecessary_suppression_exclusions = none
-
-#### C# Coding Conventions ####
-[*.cs]
-
-# var preferences
-csharp_style_var_elsewhere = false:silent
-csharp_style_var_for_built_in_types = false:silent
-csharp_style_var_when_type_is_apparent = false:silent
-
-# Expression-bodied members
-csharp_style_expression_bodied_accessors = true:silent
-csharp_style_expression_bodied_constructors = false:silent
-csharp_style_expression_bodied_indexers = true:silent
-csharp_style_expression_bodied_lambdas = true:suggestion
-csharp_style_expression_bodied_local_functions = false:silent
-csharp_style_expression_bodied_methods = false:silent
-csharp_style_expression_bodied_operators = false:silent
-csharp_style_expression_bodied_properties = true:silent
-
-# Pattern matching preferences
-csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
-csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
-csharp_style_prefer_not_pattern = true:suggestion
-csharp_style_prefer_pattern_matching = true:silent
-csharp_style_prefer_switch_expression = true:suggestion
-
-# Null-checking preferences
-csharp_style_conditional_delegate_call = true:suggestion
-
-# Modifier preferences
-csharp_prefer_static_local_function = true:warning
-csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
-
-# Code-block preferences
-csharp_prefer_braces = true:silent
-csharp_prefer_simple_using_statement = true:suggestion
-
-# Expression-level preferences
-csharp_prefer_simple_default_expression = true:suggestion
-csharp_style_deconstructed_variable_declaration = true:suggestion
-csharp_style_inlined_variable_declaration = true:suggestion
-csharp_style_pattern_local_over_anonymous_function = true:suggestion
-csharp_style_prefer_index_operator = true:suggestion
-csharp_style_prefer_range_operator = true:suggestion
-csharp_style_throw_expression = true:suggestion
-csharp_style_unused_value_assignment_preference = discard_variable:suggestion
-csharp_style_unused_value_expression_statement_preference = discard_variable:silent
-
-# 'using' directive preferences
-csharp_using_directive_placement = outside_namespace:silent
-
-#### C# Formatting Rules ####
-
-# New line preferences
-csharp_new_line_before_catch = true
-csharp_new_line_before_else = true
-csharp_new_line_before_finally = 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
-csharp_indent_block_contents = true
-csharp_indent_braces = false
-csharp_indent_case_contents = true
-csharp_indent_case_contents_when_block = true
-csharp_indent_labels = one_less_than_current
-csharp_indent_switch_labels = true
-
-# Space preferences
-csharp_space_after_cast = false
-csharp_space_after_colon_in_inheritance_clause = true
-csharp_space_after_comma = true
-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 = false
-csharp_space_before_colon_in_inheritance_clause = true
-csharp_space_before_comma = false
-csharp_space_before_dot = false
-csharp_space_before_open_square_brackets = false
-csharp_space_before_semicolon_in_for_statement = false
-csharp_space_between_empty_square_brackets = false
-csharp_space_between_method_call_empty_parameter_list_parentheses = false
-csharp_space_between_method_call_name_and_opening_parenthesis = false
-csharp_space_between_method_call_parameter_list_parentheses = false
-csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
-csharp_space_between_method_declaration_name_and_open_parenthesis = false
-csharp_space_between_method_declaration_parameter_list_parentheses = false
-csharp_space_between_parentheses = false
-csharp_space_between_square_brackets = false
-
-# Wrapping preferences
-csharp_preserve_single_line_blocks = true
-csharp_preserve_single_line_statements = true
-
-#### Naming styles ####
-[*.{cs,vb}]
-
-# Naming rules
-
-dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
-dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
-dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
-dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
-
-dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
-dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
-dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
-
-dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
-dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
-dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
-dotnet_naming_rule.events_should_be_pascalcase.symbols = events
-dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
-
-dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
-dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
-dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
-
-dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
-dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
-dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
-
-dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
-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 = suggestion
-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 = suggestion
-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 = suggestion
-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 = suggestion
-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 = suggestion
-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 = suggestion
-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 = suggestion
-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 = suggestion
-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 = suggestion
-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 = suggestion
-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 = namespace
-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
+root = true
+
+# All files
+[*]
+indent_style = space
+max_line_length = 120
+
+# Xml files
+[*.xml]
+indent_size = 2
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+tab_width = 4
+
+# New line preferences
+end_of_line = crlf
+insert_final_newline = true
+
+#### Custom Analyzer Rules ####
+# Until https://github.com/SonarSource/sonar-dotnet/issues/7624 is shipped.
+dotnet_diagnostic.S3604.severity = none
+
+dotnet_diagnostic.SA0001.severity = none
+dotnet_diagnostic.SA1010.severity = none
+dotnet_diagnostic.SA1600.severity = none
+dotnet_diagnostic.CS1591.severity = none
+dotnet_diagnostic.SA1008.severity = suggestion
+dotnet_diagnostic.SA1009.severity = suggestion
+dotnet_diagnostic.SA1101.severity = none
+dotnet_diagnostic.SA1106.severity = suggestion
+dotnet_diagnostic.SA1118.severity = none
+dotnet_diagnostic.SX1101.severity = suggestion
+dotnet_diagnostic.SA1309.severity = none
+dotnet_diagnostic.SX1309.severity = warning
+dotnet_diagnostic.SA1313.severity = suggestion
+dotnet_diagnostic.SA1501.severity = suggestion
+dotnet_diagnostic.SA1503.severity = suggestion
+dotnet_diagnostic.SA1623.severity = none
+dotnet_diagnostic.SA1633.severity = none
+dotnet_diagnostic.SA1642.severity = none
+dotnet_diagnostic.SA1643.severity = none
+dotnet_diagnostic.SA1649.severity = none
+dotnet_diagnostic.RCS1029.severity = none
+dotnet_diagnostic.RCS1090.severity = none
+
+#### Custom Coding Conventions ####
+resharper_csharp_space_in_singleline_accessorholder = true
+resharper_csharp_space_between_accessors_in_singleline_property = true
+resharper_csharp_space_in_singleline_method = true
+resharper_csharp_space_in_singleline_anonymous_method = true
+resharper_csharp_space_within_single_line_array_initializer_braces = true
+resharper_csharp_trailing_comma_in_multiline_lists = true
+resharper_csharp_place_type_attribute_on_same_line = false
+resharper_csharp_place_attribute_on_same_line = false
+
+#### .NET Coding Conventions ####
+[*.{cs,vb}]
+
+# Organize usings
+dotnet_separate_import_directive_groups = true
+dotnet_sort_system_directives_first = true
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_property = false:silent
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+
+# Field preferences
+dotnet_style_readonly_field = true:warning
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all:suggestion
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+#### C# Coding Conventions ####
+[*.cs]
+
+# var preferences
+csharp_style_var_elsewhere = false:silent
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_pattern_matching = true:silent
+csharp_style_prefer_switch_expression = true:suggestion
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Modifier preferences
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:suggestion
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_pattern_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = 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
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+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 = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+[*.{cs,vb}]
+
+# Naming rules
+
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
+dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
+dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
+
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
+
+dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
+dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
+dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.events_should_be_pascalcase.symbols = events
+dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
+dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
+dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
+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 = suggestion
+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 = suggestion
+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 = suggestion
+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 = suggestion
+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 = suggestion
+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 = suggestion
+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 = suggestion
+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 = suggestion
+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 = suggestion
+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 = suggestion
+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 = namespace
+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
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..c42c3331
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+# Normalize line endings in Git: https://www.aleksandrhovhannisyan.com/blog/crlf-vs-lf-normalizing-line-endings-in-git/
+* text=auto
+*.cs text eol=crlf
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index d39bd205..f06cb016 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -1,44 +1,44 @@
-name: Bug Report
-description: "Create a report to help fix a problem."
-title: "[bug]: "
-labels: ["bug"]
-body:
-- type: markdown
- attributes:
- value: |
- Thanks for taking the time to fill out this bug report!
-- type: textarea
- id: description
- attributes:
- label: Describe the bug
- description: A clear and concise description of what the bug is.
- validations:
- required: true
-- type: textarea
- id: reproduce
- attributes:
- label: To reproduce
- description: Steps to reproduce the behaviour
- placeholder: |
- Steps to reproduce the behavior:
- 1. Go to '...'
- 2. Click on '....'
- 3. Scroll down to '....'
- 4. See error
- validations:
- required: true
-- type: textarea
- id: expected
- attributes:
- label: Expected behavior
- description: A clear and concise description of what you expected to happen.
-- type: textarea
- id: screenshots
- attributes:
- label: Screenshots
- description: If applicable, add screenshots to help explain your problem.
-- type: textarea
- id: additional
- attributes:
- label: Additional Context
- description: Please add any other infos that could be useful.
+name: Bug Report
+description: "Create a report to help fix a problem."
+title: "[bug]: "
+labels: ["bug"]
+body:
+- type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report!
+- type: textarea
+ id: description
+ attributes:
+ label: Describe the bug
+ description: A clear and concise description of what the bug is.
+ validations:
+ required: true
+- type: textarea
+ id: reproduce
+ attributes:
+ label: To reproduce
+ description: Steps to reproduce the behaviour
+ placeholder: |
+ Steps to reproduce the behavior:
+ 1. Go to '...'
+ 2. Click on '....'
+ 3. Scroll down to '....'
+ 4. See error
+ validations:
+ required: true
+- type: textarea
+ id: expected
+ attributes:
+ label: Expected behavior
+ description: A clear and concise description of what you expected to happen.
+- type: textarea
+ id: screenshots
+ attributes:
+ label: Screenshots
+ description: If applicable, add screenshots to help explain your problem.
+- type: textarea
+ id: additional
+ attributes:
+ label: Additional Context
+ description: Please add any other infos that could be useful.
diff --git a/.github/ISSUE_TEMPLATE/documentation.yaml b/.github/ISSUE_TEMPLATE/documentation.yaml
index ad2905a0..2cfa4a20 100644
--- a/.github/ISSUE_TEMPLATE/documentation.yaml
+++ b/.github/ISSUE_TEMPLATE/documentation.yaml
@@ -1,12 +1,12 @@
-name: Documentation
-description: "Suggest a topic that is not correctly documented (or not documented at all)"
-title: "[docs]: "
-labels: ["documentation"]
-body:
-- type: textarea
- id: description
- attributes:
- label: Describe the missing piece of documentation
- description: Describe what you miss in the docs (or what is wrong).
- validations:
- required: true
+name: Documentation
+description: "Suggest a topic that is not correctly documented (or not documented at all)"
+title: "[docs]: "
+labels: ["documentation"]
+body:
+- type: textarea
+ id: description
+ attributes:
+ label: Describe the missing piece of documentation
+ description: Describe what you miss in the docs (or what is wrong).
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml
index 8639a4f6..55a9b0ca 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yaml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yaml
@@ -1,26 +1,26 @@
-name: Feature Request
-description: "Suggest a new feature for this project."
-title: "[feature]: "
-labels: ["enhancement"]
-body:
-- type: markdown
- attributes:
- value: |
- Thanks for taking the time to fill out this feature request!
-- type: textarea
- id: description
- attributes:
- label: Is your feature request related to a problem? Please describe.
- description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-- type: textarea
- id: solution
- attributes:
- label: Describe the solution you would like
- description: A clear and concise description of what you want to happen.
- validations:
- required: true
-- type: textarea
- id: additional
- attributes:
- label: Additional Context
- description: Please add any other infos that could be useful.
+name: Feature Request
+description: "Suggest a new feature for this project."
+title: "[feature]: "
+labels: ["enhancement"]
+body:
+- type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this feature request!
+- type: textarea
+ id: description
+ attributes:
+ label: Is your feature request related to a problem? Please describe.
+ description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+- type: textarea
+ id: solution
+ attributes:
+ label: Describe the solution you would like
+ description: A clear and concise description of what you want to happen.
+ validations:
+ required: true
+- type: textarea
+ id: additional
+ attributes:
+ label: Additional Context
+ description: Please add any other infos that could be useful.
diff --git a/.releaserc.json b/.releaserc.json
index 346a8edb..2b7563d3 100644
--- a/.releaserc.json
+++ b/.releaserc.json
@@ -5,6 +5,14 @@
"name": "maintenance/7.x",
"range": "7.x"
},
+ {
+ "name": "maintenance/8.x",
+ "range": "8.x"
+ },
+ {
+ "name": "maintenance/9.x",
+ "range": "9.x"
+ },
{
"name": "main",
"prerelease": "pre"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d814d829..ae2bb2d8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,63 +1,63 @@
-# Contributing to KubeOps
-
-First of all, thank you for considering contributing to KubeOps.
-This is an open souce project and shall be driven by the community.
-
-This document describes how contributions may be done and what is required
-to develop on KubeOps.
-
-## Creating/Reporting Issues
-
-Feel free to open an issue in the [issues section](https://github.com/buehler/dotnet-operator-sdk/issues).
-There are three issue templates:
-- Bug: to report an issue/bug that prevents usage or is an inconvenience of KubeOps
-- Feature request: to report a new feature that would enhance KubeOps
-- Documentation: to report missing / wrong documentation
-
-Please search through the already created issues to find similarities.
-
-## Creating Pull Requests
-
-To directly contribute to the solution, create a fork of the repository
-and implement your addition. Please keep in mind that reviewing takes some
-time and is not done instantly.
-
-Please adhere to the linting rules and the general code style in the repository.
-Also, add tests for your changes to ensure that the system works well
-when other changes happen.
-
-The PR can have any name, but it would be nice if you adhere to
-the repositories standard naming. Please name your PR
-with [Convential Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary).
-
-**NOTE for breaking changes**: please state breaking changes
-in the PR description. The review process will be faster when
-breaking changes are well documented.
-
-A few examples:
-- "fix: Null exception during watcher process"
-- "feat(core): Add new functionality"
-- "feat(testing): expose kubernetes client for testing"
-- "refactor: changed this and that"
-- "docs: Add docs about KubeOps core"
-
-The PR will be squashed and merged into the default branch.
-
-## Local Development
-
-To setup a local development environment, you'll need to perform the follwing steps:
-
-- Check out the repository (or your fork)
-- If you want to run the Operator locally, you'll need some Kubernetes instance.
- This can be any Kubernetes instance you'd like:
- - Local Kubernetes in Docker for Mac/Windows
- - minikube / any other local Kubernetes
- - Deployed Kubernetes (e.g. GCP Kubernetes instance)
-- You can now code your stuff.
-- `tests/KubeOps.TestOperator` is a developed small operator that can be run
- locally to test your implementations.
-- Write tests for your changes
-- Build the whole solution and check for linting errors / warnings.
- **NOTE** that any warning will result in an error when building
- with `Release` configuration.
-- Do not change the linting rules without creating a discussion/issue first.
+# Contributing to KubeOps
+
+First of all, thank you for considering contributing to KubeOps.
+This is an open souce project and shall be driven by the community.
+
+This document describes how contributions may be done and what is required
+to develop on KubeOps.
+
+## Creating/Reporting Issues
+
+Feel free to open an issue in the [issues section](https://github.com/buehler/dotnet-operator-sdk/issues).
+There are three issue templates:
+- Bug: to report an issue/bug that prevents usage or is an inconvenience of KubeOps
+- Feature request: to report a new feature that would enhance KubeOps
+- Documentation: to report missing / wrong documentation
+
+Please search through the already created issues to find similarities.
+
+## Creating Pull Requests
+
+To directly contribute to the solution, create a fork of the repository
+and implement your addition. Please keep in mind that reviewing takes some
+time and is not done instantly.
+
+Please adhere to the linting rules and the general code style in the repository.
+Also, add tests for your changes to ensure that the system works well
+when other changes happen.
+
+The PR can have any name, but it would be nice if you adhere to
+the repositories standard naming. Please name your PR
+with [Convential Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary).
+
+**NOTE for breaking changes**: please state breaking changes
+in the PR description. The review process will be faster when
+breaking changes are well documented.
+
+A few examples:
+- "fix: Null exception during watcher process"
+- "feat(core): Add new functionality"
+- "feat(testing): expose kubernetes client for testing"
+- "refactor: changed this and that"
+- "docs: Add docs about KubeOps core"
+
+The PR will be squashed and merged into the default branch.
+
+## Local Development
+
+To setup a local development environment, you'll need to perform the follwing steps:
+
+- Check out the repository (or your fork)
+- If you want to run the Operator locally, you'll need some Kubernetes instance.
+ This can be any Kubernetes instance you'd like:
+ - Local Kubernetes in Docker for Mac/Windows
+ - minikube / any other local Kubernetes
+ - Deployed Kubernetes (e.g. GCP Kubernetes instance)
+- You can now code your stuff.
+- `tests/KubeOps.TestOperator` is a developed small operator that can be run
+ locally to test your implementations.
+- Write tests for your changes
+- Build the whole solution and check for linting errors / warnings.
+ **NOTE** that any warning will result in an error when building
+ with `Release` configuration.
+- Do not change the linting rules without creating a discussion/issue first.
diff --git a/_old/src/KubeOps.Templates/KubeOps.Templates.csproj b/_old/src/KubeOps.Templates/KubeOps.Templates.csproj
deleted file mode 100644
index dfe2f057..00000000
--- a/_old/src/KubeOps.Templates/KubeOps.Templates.csproj
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
- true
- Template
- KubeOps.Templates
- KubeOps Templates
- dotnet-new templates Kubernetes Operator Sdk KubeOps
- dotnet new templates for KubeOps operator sdk.
-
- netstandard2.0
-
- true
- false
- content
- $(NoWarn);NU5128
-
-
-
-
-
-
-
-
diff --git a/_old/src/KubeOps.Templates/README.md b/_old/src/KubeOps.Templates/README.md
deleted file mode 100644
index b628808b..00000000
--- a/_old/src/KubeOps.Templates/README.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# KubeOps Dotnet New Templates
-
-To use the operator SDK as easy as possible, this
-[Nuget Package](https://www.nuget.org/packages/KubeOps.Templates)
-contains `dotnet new` templates.
-These templates enable developers to create Kubernetes operators
-with the simple dotnet new command in C# or F#.
-
-## Installation
-
-To install the template package, use the `dotnet` cli
-(or you may use the exact version as provided in the link above):
-
-```bash
-dotnet new --install KubeOps.Templates::*
-```
-
-As soon as the templates are installed, you may use them with:
-
-```bash
-dotnet new operator
-#or
-dotnet new operator-empty
-```
-
-Note that several of the templates are available in multiple languages
-of the .NET framework (i.e. C\# and F\#) and you may switch the
-language with the `-lang` flag of `dotnet new`.
-
-## Templates
-
-### Empty Operator
-
-_Available Languages_: C\#, F\#
-
-_Type_: Generate a project
-
-_Templatename_: `operator-empty`
-
-_Example installation_: `dotnet new operator-empty -n MyOperator`
-
-_Description_:
-This template contains the well known `Program.cs`
-and `Startup.cs` files of any other `ASP.NET` project
-and configures the startup file to use KubeObs.
-No additional code is provided.
-
-### Demo Operator
-
-_Available Languages_: C\#, F\#
-
-_Type_: Generate a project
-
-_Templatename_: `operator`
-
-_Example installation_: `dotnet new operator -n MyOperator`
-
-_Description_:
-This template contains the well known `Program.cs`
-and `Startup.cs` files of any other `ASP.NET` project
-and configures the startup file to use KubeObs.
-In addition to the empty operator, an example file
-for each "concept" is provided. You'll find an
-example implementation of:
-
-- A resource controller
-- A custom entity (that generates a CRD)
-- A finalizer
-- A validation webhook
-- A mutation webhook
-
-This template is meant to show all possible elements
-of KubeOps in one go.
diff --git a/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/.template.config/template.json b/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/.template.config/template.json
deleted file mode 100644
index 95571791..00000000
--- a/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/.template.config/template.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
- "$schema": "http://json.schemastore.org/template",
- "author": "Christoph Bühler",
- "classifications": [
- "Kubernetes",
- "Operator",
- "Empty"
- ],
- "identity": "KubeOps.Templates.EmptyOperator.CSharp",
- "groupIdentity": "KubeOps.Templates.EmptyOperator",
- "name": "Kubernetes Operator Empty",
- "description": "Create an empty kubernetes operator with the KubeOps SDK",
- "shortName": "operator-empty",
- "tags": {
- "language": "C#",
- "type": "project"
- },
- "sourceName": "GeneratedOperatorProject",
- "defaultName": "Operator",
- "preferNameDirectory": true,
- "postActions": [
- {
- "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814",
- "description": "Add KubeOps SDK reference",
- "continueOnError": false,
- "manualInstructions": [
- {
- "text": "Add the KubeOps package to your project via nuget"
- }
- ],
- "args": {
- "referenceType": "package",
- "reference": "KubeOps",
- "projectFileExtensions": ".csproj"
- }
- }
- ]
-}
diff --git a/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/GeneratedOperatorProject.csproj b/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/GeneratedOperatorProject.csproj
deleted file mode 100644
index ed0382f2..00000000
--- a/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/GeneratedOperatorProject.csproj
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- net6.0
- 10.0
- enable
- true
-
-
-
diff --git a/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/Program.cs b/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/Program.cs
deleted file mode 100644
index fa905257..00000000
--- a/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/Program.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using KubeOps.Operator;
-
-var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddKubernetesOperator();
-
-var app = builder.Build();
-app.UseKubernetesOperator();
-await app.RunOperatorAsync(args);
diff --git a/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/appsettings.Development.json b/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/appsettings.Development.json
deleted file mode 100644
index 8983e0fc..00000000
--- a/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/appsettings.Development.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
- }
- }
-}
diff --git a/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/appsettings.json b/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/appsettings.json
deleted file mode 100644
index d9d9a9bf..00000000
--- a/_old/src/KubeOps.Templates/Templates/EmptyOperator.CSharp/appsettings.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
- }
- },
- "AllowedHosts": "*"
-}
diff --git a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/.template.config/template.json b/_old/src/KubeOps.Templates/Templates/Operator.CSharp/.template.config/template.json
deleted file mode 100644
index 4ae22cf6..00000000
--- a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/.template.config/template.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "$schema": "http://json.schemastore.org/template",
- "author": "Christoph Bühler",
- "classifications": [
- "Kubernetes",
- "Operator"
- ],
- "identity": "KubeOps.Templates.Operator.CSharp",
- "groupIdentity": "KubeOps.Templates.Operator",
- "name": "Kubernetes Operator",
- "description": "Create a kubernetes operator with demo implementation",
- "shortName": "operator",
- "tags": {
- "language": "C#",
- "type": "project"
- },
- "sourceName": "GeneratedOperatorProject",
- "defaultName": "Operator",
- "preferNameDirectory": true,
- "postActions": [
- {
- "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814",
- "description": "Add KubeOps SDK reference",
- "continueOnError": false,
- "manualInstructions": [
- {
- "text": "Add the KubeOps package to your project via nuget"
- }
- ],
- "args": {
- "referenceType": "package",
- "reference": "KubeOps",
- "projectFileExtensions": ".csproj"
- }
- }
- ]
-}
diff --git a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Controller/DemoController.cs b/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Controller/DemoController.cs
deleted file mode 100644
index 784cc431..00000000
--- a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Controller/DemoController.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using k8s.Models;
-using KubeOps.Operator.Controller;
-using KubeOps.Operator.Controller.Results;
-using KubeOps.Operator.Finalizer;
-using KubeOps.Operator.Rbac;
-using GeneratedOperatorProject.Entities;
-using GeneratedOperatorProject.Finalizer;
-
-namespace GeneratedOperatorProject.Controller;
-
-[EntityRbac(typeof(V1DemoEntity), Verbs = RbacVerb.All)]
-public class DemoController : IResourceController
-{
- private readonly ILogger _logger;
- private readonly IFinalizerManager _finalizerManager;
-
- public DemoController(ILogger logger, IFinalizerManager finalizerManager)
- {
- _logger = logger;
- _finalizerManager = finalizerManager;
- }
-
- public async Task ReconcileAsync(V1DemoEntity entity)
- {
- _logger.LogInformation($"entity {entity.Name()} called {nameof(ReconcileAsync)}.");
- await _finalizerManager.RegisterFinalizerAsync(entity);
-
- return ResourceControllerResult.RequeueEvent(TimeSpan.FromSeconds(15));
- }
-
- public Task StatusModifiedAsync(V1DemoEntity entity)
- {
- _logger.LogInformation($"entity {entity.Name()} called {nameof(StatusModifiedAsync)}.");
-
- return Task.CompletedTask;
- }
-
- public Task DeletedAsync(V1DemoEntity entity)
- {
- _logger.LogInformation($"entity {entity.Name()} called {nameof(DeletedAsync)}.");
-
- return Task.CompletedTask;
- }
-}
diff --git a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Entities/V1DemoEntity.cs b/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Entities/V1DemoEntity.cs
deleted file mode 100644
index 2da4970a..00000000
--- a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Entities/V1DemoEntity.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using k8s.Models;
-using KubeOps.Operator.Entities;
-
-namespace GeneratedOperatorProject.Entities;
-
-[KubernetesEntity(Group = "demo.kubeops.dev", ApiVersion = "v1", Kind = "DemoEntity")]
-public class V1DemoEntity : CustomKubernetesEntity
-{
- public class V1DemoEntitySpec
- {
- public string Username { get; set; } = string.Empty;
- }
-
- public class V1DemoEntityStatus
- {
- public string DemoStatus { get; set; } = string.Empty;
- }
-}
diff --git a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Finalizer/DemoFinalizer.cs b/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Finalizer/DemoFinalizer.cs
deleted file mode 100644
index 447e5a10..00000000
--- a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Finalizer/DemoFinalizer.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using k8s.Models;
-using KubeOps.Operator.Finalizer;
-using GeneratedOperatorProject.Entities;
-
-namespace GeneratedOperatorProject.Finalizer;
-
-public class DemoFinalizer : IResourceFinalizer
-{
- private readonly ILogger _logger;
-
- public DemoFinalizer(ILogger logger)
- {
- _logger = logger;
- }
-
- public Task FinalizeAsync(V1DemoEntity entity)
- {
- _logger.LogInformation($"entity {entity.Name()} called {nameof(FinalizeAsync)}.");
-
- return Task.CompletedTask;
- }
-}
diff --git a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/GeneratedOperatorProject.csproj b/_old/src/KubeOps.Templates/Templates/Operator.CSharp/GeneratedOperatorProject.csproj
deleted file mode 100644
index ed0382f2..00000000
--- a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/GeneratedOperatorProject.csproj
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- net6.0
- 10.0
- enable
- true
-
-
-
diff --git a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Program.cs b/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Program.cs
deleted file mode 100644
index fa905257..00000000
--- a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Program.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using KubeOps.Operator;
-
-var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddKubernetesOperator();
-
-var app = builder.Build();
-app.UseKubernetesOperator();
-await app.RunOperatorAsync(args);
diff --git a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Webhooks/DemoMutator.cs b/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Webhooks/DemoMutator.cs
deleted file mode 100644
index c8cf9d36..00000000
--- a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Webhooks/DemoMutator.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using KubeOps.Operator.Webhooks;
-using GeneratedOperatorProject.Entities;
-
-namespace GeneratedOperatorProject.Webhooks;
-
-public class DemoMutator : IMutationWebhook
-{
- public AdmissionOperations Operations => AdmissionOperations.Create;
-
- public MutationResult Create(V1DemoEntity newEntity, bool dryRun)
- {
- newEntity.Spec.Username = "not foobar";
- return MutationResult.Modified(newEntity);
- }
-}
diff --git a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Webhooks/DemoValidator.cs b/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Webhooks/DemoValidator.cs
deleted file mode 100644
index a6109f5c..00000000
--- a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/Webhooks/DemoValidator.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using KubeOps.Operator.Webhooks;
-using GeneratedOperatorProject.Entities;
-
-namespace GeneratedOperatorProject.Webhooks;
-
-public class DemoValidator : IValidationWebhook
-{
- public AdmissionOperations Operations => AdmissionOperations.Create;
-
- public ValidationResult Create(V1DemoEntity newEntity, bool dryRun)
- => newEntity.Spec.Username == "forbiddenUsername"
- ? ValidationResult.Fail(StatusCodes.Status400BadRequest, "Username is forbidden")
- : ValidationResult.Success();
-}
diff --git a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/appsettings.Development.json b/_old/src/KubeOps.Templates/Templates/Operator.CSharp/appsettings.Development.json
deleted file mode 100644
index 8983e0fc..00000000
--- a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/appsettings.Development.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
- }
- }
-}
diff --git a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/appsettings.json b/_old/src/KubeOps.Templates/Templates/Operator.CSharp/appsettings.json
deleted file mode 100644
index d9d9a9bf..00000000
--- a/_old/src/KubeOps.Templates/Templates/Operator.CSharp/appsettings.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
- }
- },
- "AllowedHosts": "*"
-}
diff --git a/_old/src/KubeOps/README.md b/_old/src/KubeOps/README.md
deleted file mode 100644
index 9bf9d68b..00000000
--- a/_old/src/KubeOps/README.md
+++ /dev/null
@@ -1,1154 +0,0 @@
-# KubeOps - Kubernetes Operator SDK
-
-This package (sadly "DotnetOperatorSdk" is already taken on nuget, so its "KubeOps")
-is a kubernetes operator sdk written in dotnet. It is heavily inspired by
-["kubebuilder"](https://github.com/kubernetes-sigs/kubebuilder)
-that provides the same and more functions for kubernetes operators in GoLang.
-
-## Getting Started
-
-This document should describe what steps you need to follow, to fire up your own operator.
-This covers the basic installation of the operator sdk, further
-clarification / documentation is in the specific sections.
-
-The operator sdk is designed as an extension to the Generic Web Host of Microsoft.
-So you'll find method extensions for `IServiceCollection` and `IApplicationBuilder`
-that activate and start the operator as a web application.
-
-### Terminology
-
-- `Entity`: A (C#) model - an entity - that is used in kubernetes.
- An entity is the class for a kubernetes resource.
-- `Resource` (or `TResource`): The type of a kubernetes resource.
-- `Controller` or `ResourceController`: An instance of a resource manager
- that is responsible for the reconciliation of an entity.
-- `Finalizer`: A special resource manager that is attached to the entity
- via identifier. The finalizers are called when an entity is deleted
- on kubernetes.
-- `Validator`: An implementation for a validation admission webhook.
-- `CRD`: CustomResourceDefinition of kubernetes.
-
-### How To Start
-
-Using this sdk is pretty simple:
-
-- Create a new asp.net core application
-- Install the package
-- Replace the `Run` function in `Program.cs`
-- Add the operator to `Startup.cs`
-- Write entities / controllers / finalizers
-- Go.
-
-> If you don't create an asp.net core application (template)
-> please note that the output type of the application must be an "exe":
-> `Exe`
-
-#### Install the package
-
-```bash
-dotnet add package KubeOps
-```
-
-That's it.
-
-#### Update Entrypoint
-
-In your `Program.cs` file, replace `Build().Run()` with `Build().RunOperatorAsync(args)`:
-
-```csharp
-public static class Program
-{
- public static Task Main(string[] args) =>
- CreateHostBuilder(args)
- .Build()
- .RunOperatorAsync(args);
-
- private static IHostBuilder CreateHostBuilder(string[] args) =>
- Host.CreateDefaultBuilder(args)
- .ConfigureWebHostDefaults(webBuilder =>
- {
- webBuilder.UseStartup();
- });
-}
-```
-
-This adds the default commands (like run and the code generators) to your app.
-The commands are documentated under the [CLI Commands](#commands) section.
-
-> Technically you don't need to replace the function,
-> but if you don't, the other commands like yaml generation
-> are not available to your application. Also namespacing is not
-> possible via run flag.
-
-#### Add to Startup.cs
-
-```csharp
-public class Startup
-{
- /* snip... */
- public void ConfigureServices(IServiceCollection services)
- {
- services
- .AddKubernetesOperator(); // config / settings here
-
- // your own dependencies
- services.AddTransient();
- }
-
- public void Configure(IApplicationBuilder app)
- {
- // fire up the mappings for the operator
- // this is technically not needed, but if you don't call this
- // function, the healthchecks and mappings are not
- // mapped to endpoints (therefore not callable)
- app.UseKubernetesOperator();
- }
-}
-```
-
-## Features
-
-As of now, the operator sdk supports - roughly - the following features:
-
-- Entities
- - Normal entities
- - Multi version entities
-- Controller with all operations of an entity
- - Reconcile
- - StatusModified
- - Deleted
-- Finalizers for entities
-- Webhooks
- - Validation / validators
-- Prometheus metrics for queues / caches / watchers
-- Healthchecks, split up to "readiness" and "liveness" (or both)
-- Commands for the operator (for exact documentation run: `dotnet run -- --help`)
- - `Run`: Start the operator and run the asp.net application
- - `Install`: Install the found CRD's into the actual configured
- cluster in your kubeconfig
- - `Uninstall`: Remove the CRDs from your cluster
- - `Generate CRD`: Generate the yaml for your CRDs
- - `Generate Docker`: Generate a dockerfile for your operator
- - `Generate Installer`: Generate a kustomization yaml for your operator
- - `Generate Operator`: Generate the yaml for your operator (rbac / role / etc)
- - `Generate RBAC`: Generate rbac roles for your CRDs
-
-Other features and ideas are listed in the repository's
-["issues"](https://github.com/buehler/dotnet-operator-sdk/issues).
-
-## Settings
-
-To configure the operator, use the `OperatorSettings` instance
-that is configurable during the generic host extension method
-`AddKubernetesOperator`.
-
-You can configure things like the name of the operator,
-if it should use namespacing, and other elements like the
-urls of metrics and lease durations for the leader election.
-
-All settings are well documented in the code docs.
-
-## Custom Entities
-
-The words `entity` and `resource` are kind of interchangeable. It strongly
-depends on the context. The resource is the type of an object in kubernetes
-which is defined by the default api or a CRD. While an entity is a class
-in C# of such a resource. (CRD means "custom resource definition").
-
-To write your own kubernetes entities, use the interfaces
-provided by `k8s` or use the `CustomKubernetesEntity`.
-There are two overloads with generics for the `Spec` and `Status` resource values.
-
-A "normal" entity does not provide any real value (i.e. most of the time).
-Normally you need some kind of `Spec` to have data in your entity.
-
-The status is a subresource which can be updated without updating the
-whole resource and is a flat key-value list (or should be)
-of properties to represent the state of a resource.
-
-### Write Entities
-
-A custom entity could be:
-
-```csharp
-class FooSpec
-{
- public string? Test { get; set; }
-}
-
-[KubernetesEntity(Group = "test", ApiVersion = "v1")]
-public class Foo : CustomKubernetesEntity
-{
-}
-```
-
-Now a CRD for your "Foo" class is generated on build
-or via the cli commands.
-
-If you don't use the `CustomKubernetesEntity` base class, you need to - at least - use the appropriate interfaces from `k8s`:
-
-- `KubernetesObject`
-- `IKubernetesObject`
-
-#### Ignoring Entities
-
-There are use-cases when you want to model / watch a custom entity from another
-software engineer that are not part of the base models in `k8s`.
-
-To prevent the generator from creating yaml's for CRDs you don't own, use
-the `IgnoreEntityAttribute`.
-
-So as an example, one could try to watch for Ambassador-Mappings with
-the following entity:
-
-```csharp
-public class MappingSpec
-{
- public string Host { get; set; }
-}
-
-[IgnoreEntity]
-[KubernetesEntity(Group = "getambassador.io", ApiVersion = "v2")]
-public class Mapping : CustomKubernetesEntity
-{
-}
-```
-
-You need it to be a `KubernetesEntity` and a `IKubernetesObject`, but
-you don't want a CRD generated for it (thus the `IgnoreEntity` attribute).
-
-### RBAC
-
-The operator (SDK) will generate the role config for your
-operator to be installed. When your operator needs access to
-Kubernetes objects, they must be mentioned with the
-RBAC attributes. During build, the SDK scans the configured
-types and generates the RBAC role that the operator needs
-to function.
-
-There exist two versions of the attribute:
-`KubeOps.Operator.Rbac.EntityRbacAttribute` and
-`KubeOps.Operator.Rbac.GenericRbacAttribute`.
-
-The generic RBAC attribute will be translated into a `V1PolicyRole`
-according to the properties set in the attribute.
-
-```csharp
-[GenericRbac(Groups = new []{"apps"}, Resources = new[]{"deployments"}, Verbs = RbacVerb.All)]
-```
-
-The entity RBAC attribute is the elegant option to use
-dotnet mechanisms. The CRD information is generated out of
-the given types and then grouped by type and used RBAC verbs.
-If you create multiple attributes with the same type, they are
-concatenated.
-
-```csharp
-[EntityRbac(typeof(RbacTest1), Verbs = RbacVerb.Get | RbacVerb.Update)]
-```
-
-### Validation
-
-During CRD generation, the generated json schema uses the types
-of the properties to create the openApi schema.
-
-You can use the various validator attributes to customize your crd:
-
-(all attributes are on properties with the exception of the Description)
-
-- `Description`: Describe the property or class
-- `ExternalDocs`: Add a link to an external documentation
-- `Items`: Customize MinItems / MaxItems and if the items should be unique
-- `Length`: Customize the length of something
-- `MultipleOf`: A number should be a multiple of
-- `Pattern`: A valid ECMA script regex (e.g. `/\d*/`)
-- `RangeMaximum`: The maximum of a value (with option to exclude the max itself)
-- `RangeMinimum`: The minimum of a value (with option to exclude the min itself)
-- `Required`: The field is listed in the required fields
-- `PreserveUnknownFields`: Set the `X-Kubernetes-Preserve-Unknown-Fields` to `true`
-
-> For `Description`: if your project generates the XML documentation files
-> for the result, the crd generator also searches for those files and a possible
-> `` tag in the xml documentation. The attribute will take precedence though.
-
-```csharp
-public class MappingSpec
-{
- /// This is a comment.
- [Description("This is another comment")]
- public string Host { get; set; }
-}
-```
-
-In the example above, the text of the attribute will be used.
-
-### Multi-Version Entities
-
-You can manage multiple versions of a CRD. To do this, you can
-specify multiple classes as the "same" entity, but with different
-versions.
-
-To mark multiple entity classes as the same, use exactly the same
-`Kind`, `Group` and `PluralName` and differ in the `ApiVersion`
-field.
-
-#### Version priority
-
-Sorting of the versions - and therefore determine which version should be
-the `storage version` if no attribute is provided - is done by the kubernetes
-rules of version sorting:
-
-Priority is as follows:
-
-1. General Availablility (i.e. `V1Foobar`, `V2Foobar`)
-2. Beta Versions (i.e. `V11Beta13Foobar`, `V2Beta1Foobar`)
-3. Alpha Versions (i.e. `V16Alpha13Foobar`, `V2Alpha10Foobar`)
-
-The parsed version numbers are sorted by the highest first, this leads
-to the following version priority:
-
-```
-- v10
-- v2
-- v1
-- v11beta2
-- v10beta3
-- v3beta1
-- v12alpha1
-- v11alpha2
-```
-
-This can also be reviewed in the
-[Kubernetes documentation](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#version-priority).
-
-#### Storage Version
-
-To determine the storage version (of which one, and exactly one must exist)
-the system uses the previously mentioned version priority to sort the versions
-and picking the first one. To overwrite this behaviour, use the
-`KubeOps.Operator.Entities.Annotations.StorageVersionAttribute`
-
-> When multiple `KubeOps.Operator.Entities.Annotations.StorageVersionAttribute`
-> are used, the system will thrown an error.
-
-To overwrite a version, annotate the entity class with the attribute.
-
-#### Example
-
-##### Normal multiversion entity
-
-Note that the `Kind`
-
-```csharp
-[KubernetesEntity(
- ApiVersion = "v1",
- Kind = "VersionedEntity",
- Group = "kubeops.test.dev",
- PluralName = "versionedentities")]
-public class V1VersionedEntity : CustomKubernetesEntity
-{
-}
-
-[KubernetesEntity(
- ApiVersion = "v1beta1",
- Kind = "VersionedEntity",
- Group = "kubeops.test.dev",
- PluralName = "versionedentities")]
-public class V1Beta1VersionedEntity : CustomKubernetesEntity
-{
-}
-
-[KubernetesEntity(
- ApiVersion = "v1alpha1",
- Kind = "VersionedEntity",
- Group = "kubeops.test.dev",
- PluralName = "versionedentities")]
-public class V1Alpha1VersionedEntity : CustomKubernetesEntity
-{
-}
-```
-
-The resulting storage version would be `V1VersionedEntity`.
-
-##### Overwritten storage version multi-version entity
-
-```csharp
-[KubernetesEntity(
- ApiVersion = "v1",
- Kind = "AttributeVersionedEntity",
- Group = "kubeops.test.dev",
- PluralName = "attributeversionedentities")]
-[StorageVersion]
-public class V1AttributeVersionedEntity : CustomKubernetesEntity
-{
-}
-
-[KubernetesEntity(
- ApiVersion = "v2",
- Kind = "AttributeVersionedEntity",
- Group = "kubeops.test.dev",
- PluralName = "attributeversionedentities")]
-public class V2AttributeVersionedEntity : CustomKubernetesEntity
-{
-}
-```
-
-The resulting storage version would be `V1AttributeVersionedEntity`.
-
-## Resource Controller
-
-When reconciling an entity of a `CRD`, one needs a controller to do so.
-The controller abstracts the general complexity of watching the
-resources on kubernetes and queueing of the events.
-
-When you want to create a controller for your (or any) entity,
-read the following instructions.
-
-When you have controllers, they are automatically added to the
-DI system via their `KubeOps.Operator.Controller.IResourceController` interface.
-
-Controllers are registered as **scoped** elements in the DI system.
-Which means, they basically behave like asp.net api controllers.
-You can use dependency injection with all types of dependencies.
-
-### Controller instance
-
-After you created a custom entity (like described in [Entities](#custom-entities))
-or you want to reconcile a given entity (from the `k8s.Models` namespace,
-e.g. `V1ConfigMap`) you need to create a controller class
-as you would do for a MVC or API controller in asp.net.
-
-Make sure you implement the `KubeOps.Operator.Controller.IResourceController` interface.
-
-```csharp
-[EntityRbac(typeof(MyCustomEntity), Verbs = RbacVerb.All)]
-public class FooCtrl : IResourceController
-{
- // Implement the needed methods here.
- // The interface provides default implementation which do a NOOP.
- // Possible overwrites:
- // "ReconcileAsync": when the operator sees the entity for the first time, it was modified or just fired an event,
- // "StatusModifiedAsync" (i.e. when only the status was updated),
- // "DeletedAsync" (i.e. when the entity was deleted and all finalizers are done)
-}
-```
-
-### Namespaced controller
-
-To limit the operator (and therefore all controllers) to a specific
-namespace in kubernetes, use the `KubeOps.Operator.OperatorSettings`
-and configure a specific namespace when it is predefined.
-
-To use namespacing dynamically, run the application with the `--namespaced`
-option. When given a name (i.e. `--namespaced=foobar`) the defined
-namespace is used. When only the option is provided (i.e. `--namespaced`)
-then the actual namespace is used that the pod runs in.
-
-### RBAC
-
-The entity rbac attribute does provide the information needed about
-your needed roles / rules.
-
-Please configure all entities you want to manage with your
-operator with such an entity rbac attribute. This generates
-the rbac roles / role bindings for your operator and therefore
-for the service account associated with the operator.
-
-#### EntityRbac
-
-The first possibility to configure rbac is with the `KubeOps.Operator.Rbac.EntityRbacAttribute`
-attribute.
-
-The attribute takes a list of types (your entities) and a `KubeOps.Operator.Rbac.RbacVerb`.
-The verbs define the needed permissions and roles for the given entity(ies).
-
-You can configure multiple types and even well known entities from kubernetes:
-
-```csharp
-[EntityRbac(typeof(MyCustomEntity), Verbs = RbacVerb.All)]
-[EntityRbac(typeof(V1Secret), typeof(V1ConfigMap), Verbs = RbacVerb.Get | RbacVerb.List)]
-[EntityRbac(typeof(V1Deployment), Verbs = RbacVerb.Create | RbacVerb.Update | RbacVerb.Delete)]
-```
-
-#### GenericRbac
-
-The second possibility is to use the `KubeOps.Operator.Rbac.GenericRbacAttribute`
-which takes a list of api groups, resources, versions and a selection of
-RbacVerbs to configure the rbac rule:
-
-```csharp
-[GenericRbac(Groups = new {"apps"}, Resources = new {"deployments"}, Verbs = RbacVerb.All)]
-```
-
-### Requeue
-
-The controller's methods (reconcile) have
-a return value of `KubeOps.Operator.Controller.Results.ResourceControllerResult`.
-There are multiple ways how a result of a controller can be created:
-
-- `null`: The controller will not requeue your entity / event.
-- `KubeOps.Operator.Controller.Results.ResourceControllerResult.RequeueEvent`:
- Return a result object with a `System.TimeSpan` that will requeue
- the event and the entity after the time has passed.
-
-The requeue mechanism can be useful if you want to periodically check for a database
-connection for example and update the status of a given entity.
-
-```csharp
-/* snip... */
-public Task CreatedAsync(V1TestEntity resource)
-{
- return Task.FromResult(ResourceControllerResult.RequeueEvent(TimeSpan.FromSeconds(15)); // This will requeue the event in 15 seconds.
-}
-
-public Task CreatedAsync(V1TestEntity resource)
-{
- return Task.FromResult(null); // This wont trigger a requeue.
-}
-/* snip... */
-```
-
-### Error requeue
-
-If the function throws an error, the event is requeued with an exponential backoff.
-
-```csharp
-/* snip... */
-public Task CreatedAsync(V1TestEntity resource)
- // do something useful.
- throw new Exception("¯\\_(ツ)_/¯");
-}
-/* snip... */
-```
-
-Each event that errors will be retried **four times**.
-
-## Events / Event Series
-
-Kubernetes knows "Events" which can be sort of attached to a resource
-(i.e. a Kubernetes object).
-
-To create and use events, inject the @"KubeOps.Operator.Events.IEventManager"
-into your controller. It is registered as a transient resource in the DI
-container.
-
-### IEventManager
-
-#### Publish events
-
-The event manager allows you to either publish an event that you created
-by yourself, or helps you publish events with predefined data.
-
-If you want to use the helper:
-
-```c#
-// fetch from DI, or inject into your controller.
-IEventManager manager = services.GetRequiredService;
-
-// Publish the event.
-// This creates an event and publishes it.
-// If the event was previously published, it is fetched
-// and the "count" number is increased. This essentially
-// creates an event-series.
-await manager.PublishAsync(resource, "reason", "my fancy message");
-```
-
-If you want full control over the event:
-
-```c#
-// fetch from DI, or inject into your controller.
-IEventManager manager = services.GetRequiredService;
-
-var @event = new Corev1Event
- {
- // ... fill out all fields.
- }
-
-// Publish the event.
-// This essentially calls IKubernetesClient.Save.
-await manager.PublishAsync(@event);
-```
-
-#### Use publisher delegates
-
-If you don't want to call the `KubeOps.Operator.Events.IEventManager.PublishAsync`
-all the time with the same arguments, you can create delegates.
-
-There exist two different delegates:
-
-- "AsyncStaticPublisher": Predefined event
- on a predefined resource.
-- "AsyncPublisher": Predefined event
- on a variable resource.
-
-To use the static publisher:
-
-```c#
-var publisher = manager.CreatePublisher(resource, "reason", "message");
-await publisher();
-
-// and later on:
-await publisher(); // again without specifying reason / message and so on.
-```
-
-To use the dynamic publisher:
-
-```c#
-var publisher = manager.CreatePublisher("reason", "message");
-await publisher(resource);
-
-// and later on:
-await publisher(resource); // again without specifying reason / message and so on.
-```
-
-The dynamic publisher can be used to predefine the event for your resources.
-
-As an example in a controller:
-
-```c#
-public class TestController : IResourceController
-{
- private readonly IEventManager.Publisher _publisher;
-
- public TestController(IEventManager eventManager)
- {
- _publisher = eventManager.CreatePublisher("reason", "my fancy message");
- }
-
- public Task CreatedAsync(V1TestEntity resource)
- {
- // Here, the event is published with predefined strings
- // but for a "variable" resource.
- await _publisher(resource);
- return Task.FromResult(null);
- }
-}
-```
-
-## Finalizers
-
-A finalizer is a special type of software that can asynchronously
-cleanup stuff for an entity that is being deleted.
-
-A finalizer is registered as an identifier in a kubernetes
-object (i.e. in the yaml / json structure) and the object
-wont be removed from the api until all finalizers are removed.
-
-If you write finalizer, they will be automatically added to the
-DI system via their type `KubeOps.Operator.Finalizer.IResourceFinalizer`
-
-### Write a finalizer
-
-Use the correct interface (`KubeOps.Operator.Finalizer.IResourceFinalizer`).
-
-A finalizer can be as simple as:
-
-```csharp
-public class TestEntityFinalizer : IResourceFinalizer
-{
- private readonly IManager _manager;
-
- public TestEntityFinalizer(IManager manager)
- {
- _manager = manager;
- }
-
- public Task FinalizeAsync(V1TestEntity resource)
- {
- _manager.Finalized(resource);
- return Task.CompletedTask;
- }
-}
-```
-
-The interface also provides a way of overwriting the `Identifier` of the finalizer if you feed like it.
-
-When the finalizer successfully completed his job, it is automatically removed
-from the finalizers list of the entity. The finalizers are registered
-as scoped resources in DI.
-
-### Register a finalizer
-
-To attach a finalizer for a resource, call the
-`KubeOps.Operator.Finalizer.IFinalizerManager.RegisterFinalizerAsync`
-method in the controller during reconciliation.
-
-```csharp
-public class TestController : IResourceController
-{
- private readonly IFinalizerManager _manager;
-
- public TestController(IFinalizerManager manager)
- {
- _manager = manager;
- }
-
- public async Task CreatedAsync(V1TestEntity resource)
- {
- // The type MyFinalizer must be an IResourceFinalizer
- await _manager.RegisterFinalizerAsync(resource);
- return null;
- }
-}
-```
-
-Alternatively, the `KubeOps.Operator.Finalizer.IFinalizerManager.RegisterAllFinalizersAsync`
-method can be used to attach all finalizers known to the operator for that entity type.
-
-```csharp
-public class TestController : IResourceController
-{
- private readonly IFinalizerManager _manager;
-
- public TestController(IFinalizerManager manager)
- {
- _manager = manager;
- }
-
- public async Task CreatedAsync(V1TestEntity resource)
- {
- await _manager.RegisterAllFinalizersAsync(resource);
- return null;
- }
-}
-```
-
-### Unregistering a finalizer
-
-When a resource is finalized, the finalizer is removed automatically.
-However, if you want to remove a finalizer before a resource is deleted/finalized,
-you can use `KubeOps.Operator.Finalizer.IFinalizerManager.RemoveFinalizerAsync`.
-
-```csharp
-public class TestController : IResourceController
-{
- private readonly IFinalizerManager _manager;
-
- public TestController(IFinalizerManager manager)
- {
- _manager = manager;
- }
-
- public async Task CreatedAsync(V1TestEntity resource)
- {
- await _manager.RemoveFinalizerAsync(resource);
- return null;
- }
-}
-```
-
-## Webhooks
-
-Kubernetes supports various webhooks to extend the normal api behaviour
-of the master api. Those are documented on the
-[kubernetes website](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/).
-
-`KubeOps` supports the following webhooks out of the box:
-
-- Validator / Validation
-- Mutator / Mutation
-
-The following documentation should give the user an overview
-on how to implement a webhook what this implies to the written operator.
-
-At the courtesy of the kubernetes website, here is a diagram of the
-process that runs for admission controllers and api requests:
-
-![admission controller phases](https://d33wubrfki0l68.cloudfront.net/af21ecd38ec67b3d81c1b762221b4ac777fcf02d/7c60e/images/blog/2019-03-21-a-guide-to-kubernetes-admission-controllers/admission-controller-phases.png)
-
-### General
-
-In general, if your operator contains _any_ registered (registered in the
-DI) the build process that is provided via `KubeOps.targets` will
-generate a CA certificate for you.
-
-So if you add a webhook to your operator the following changes
-to the normal deployment of the operator will happen:
-
-1. During "after build" phase, the sdk will generate
- a CA-certificate for self signed certificates for you.
-2. The ca certificate and the corresponding key are added
- to the deployment via kustomization config.
-3. A special config is added to the deployment via
- kustomization to use https.
-4. The deployment of the operator now contains an `init-container`
- that loads the `ca.pem` and `ca-key.pem` files and creates
- a server certificate. Also, a service and the corresponding
- webhook configurations are created.
-5. When the operator starts, an additional https route is registered
- with the created server certificate.
-
-When a webhook is registered, the specified operations will
-trigger a POST call to the operator.
-
-> The certificates are generated with [cfssl](https://github.com/cloudflare/cfssl),
-> an amazing tool from cloudflare that helps with the general hassle
-> of creating CAs and certificates in general.
-
-> Make sure you commit the `ca.pem` / `ca-key.pem` file.
-> During operator startup (init container) those files
-> are needed. Since this represents a self signed certificate,
-> and it is only used for cluster internal communication,
-> it is no security issue to the system. The service is not
-> exposed to the internet.
-
-> The `server.pem` and `server-key.pem` files are generated
-> in the init container during pod startup.
-> Each pod / instance of the operator gets its own server
-> certificate but the CA must be shared among them.
-
-### Local development
-
-It is possible to test / debug webhooks locally. For this, you need
-to implement the webhook and use assembly-scanning (or the
-operator builder if you disabled scanning) to register
-the webhook type.
-
-There are two possibilities to tell Kubernetes that it should
-call your local running operator for the webhooks. The url
-that Kubernetes addresses _must_ be an HTTPS address.
-
-#### Using `AddWebhookLocaltunnel`
-
-In your `Startup.cs` you can use the `IOperatorBuilder`
-method `AddWebhookLocaltunnel` to add an automatic
-localtunnel instance to your operator.
-
-This will cause the operator to register a hosted service that
-creates a tunnel and then registers itself to Kubernetes
-with the created proxy-url. Now all calls are automatically
-forwarded via HTTPS to your operator.
-
-```csharp
-namespace KubeOps.TestOperator
-{
- public class Startup
- {
- public void ConfigureServices(IServiceCollection services)
- {
- services
- .AddKubernetesOperator()
-#if DEBUG
- .AddWebhookLocaltunnel()
-#endif
- ;
- services.AddTransient();
- }
-
- public void Configure(IApplicationBuilder app)
- {
- app.UseKubernetesOperator();
- }
- }
-}
-```
-
-> It is _strongly_ advices against using auto-webhooks
-> with localtunnel in production. This feature
-> is intended to improve the developer experience
-> while coding operators.
-
-> Some IDEs (like Rider from JetBrains) do not correctly
-> terminate debugged applications. Hence, the
-> webhook registration remains in Kubernetes. If you remove
-> webhooks from your operator, you need to remove the
-> registration within Kubernetes as well.
-
-#### Using external proxy
-
-The operator will run on a specific http address, depending on your
-configuration.
-Now, use [ngrok](https://ngrok.com/) or
-[localtunnel](https://localtunnel.github.io/www/) or something
-similar to create a HTTPS tunnel to your local running operator.
-
-Now you can use the cli command of the sdk
-`dotnet run -- webhooks register --base-url <>` to
-register the webhooks under the tunnel's url.
-
-The result is your webhook being called by the kubernetes api.
-It is suggested one uses `Docker Desktop` with kubernetes.
-
-### Validation webhook
-
-The general idea of this webhook type is to validate an entity
-before it is definitely created / updated or deleted.
-
-Webhooks are registered in a **scoped** manner to the DI system.
-They behave like asp.net api controller.
-
-The implementation of a validator is fairly simple:
-
-- Create a class somewhere in your project.
-- Implement the @"KubeOps.Operator.Webhooks.IValidationWebhook`1" interface.
-- Define the @"KubeOps.Operator.Webhooks.IAdmissionWebhook`2.Operations"
- (from the interface) that the validator is interested in.
-- Overwrite the corresponding methods.
-
-> The interface contains default implementations for _ALL_ methods.
-> The default of the async methods are to call the sync ones.
-> The default of the sync methods is to return a "not implemented"
-> result.
-> The async methods take precedence over the synchronous ones.
-
-The return value of the validation methods are
-@"KubeOps.Operator.Webhooks.ValidationResult"
-objects. A result contains a boolean flag if the entity / operation
-is valid or not. It may contain additional warnings (if it is valid)
-that are presented to the user if the kubernetes api supports it.
-If the result is invalid, one may add a custom http status code
-as well as a custom error message that is presented to the user.
-
-#### Example
-
-```c#
-public class TestValidator : IValidationWebhook
- {
- public AdmissionOperations Operations => AdmissionOperations.Create | AdmissionOperations.Update;
-
- public ValidationResult Create(EntityClass newEntity, bool dryRun) =>
- CheckSpec(newEntity)
- ? ValidationResult.Success("The username may not be foobar.")
- : ValidationResult.Fail(StatusCodes.Status400BadRequest, @"Username is ""foobar"".");
-
- public ValidationResult Update(EntityClass _, EntityClass newEntity, bool dryRun) =>
- CheckSpec(newEntity)
- ? ValidationResult.Success("The username may not be foobar.")
- : ValidationResult.Fail(StatusCodes.Status400BadRequest, @"Username is ""foobar"".");
-
- private static bool CheckSpec(EntityClass entity) => entity.Spec.Username != "foobar";
- }
-```
-
-### Mutation webhook
-
-Mutators are similar to validators but instead of defining if an object is
-valid or not, they are able to modify an object on the fly. The result
-of a mutator may generate a JSON Patch (http://jsonpatch.com) that patches
-the object that is later passed to the validators and to the Kubernetes
-API.
-
-The implementation of a mutator is fairly simple:
-
-- Create a class somewhere in your project.
-- Implement the "KubeOps.Operator.Webhooks.IMutationWebhook" interface.
-- Define the "KubeOps.Operator.Webhooks.IAdmissionWebhook.Operations"
- (from the interface) that the validator is interested in.
-- Overwrite the corresponding methods.
-
-> The interface contains default implementations for _ALL_ methods.
-> The default of the async methods are to call the sync ones.
-> The default of the sync methods is to return a "not implemented"
-> result.
-> The async methods take precedence over the synchronous ones.
-
-The return value of the mutation methods do indicate if
-there has been a change in the model or not. If there is no
-change, return a result from "KubeOps.Operator.Webhooks.MutationResult.NoChanges"
-and if there are changes, modify the object that is passed to the
-method and return the changed object with
-"KubeOps.Operator.Webhooks.MutationResult.Modified(System.Object)".
-The system then calculates the diff and creates a JSON patch for
-the object.
-
-## Operator utils
-
-There are two basic utilities that should be mentioned:
-
-- Health-checks
-- Metrics
-
-### Healthchecks
-
-This is a basic feature of asp.net. The operator sdk makes use of
-it and splits them up into `Liveness` and `Readiness` checks.
-
-With the appropriate methods, you can add an `IHealthCheck` interface
-to either `/ready`, `/health` or both.
-
-The urls can be configured via "KubeOps.Operator.OperatorSettings".
-
-- "AddHealthCheck":
- adds a healthcheck to ready and liveness
-- "AddLivenessCheck":
- adds a healthcheck to the liveness route only
-- "AddReadinessCheck":
- adds a healthcheck to the readiness route only
-
-### Metrics
-
-By default, the operator lists some interessting metrics on the
-`/metrics` route. The url can be configured via @"KubeOps.Operator.OperatorSettings".
-
-There are many counters on how many elements have been reconciled, if the
-controllers and queues are up and how many elements are in timed requeue state.
-
-Please have a look at the metrics if you run your operator locally or online
-to see which metrics are available.
-
-Of course you can also have a look at the used metrics classes to see the
-implementation: [Metrics Implementations](https://github.com/buehler/dotnet-operator-sdk/tree/master/src/KubeOps/Operator/DevOps).
-
-## Entity / Resource utils
-
-There are several method extensions that help with day to day resource
-handling. Head over to their documentation to see that they do:
-
-- `KubeOps.Operator.Entities.Extensions.KubernetesObjectExtensions.MakeObjectReference`
-- `KubeOps.Operator.Entities.Extensions.KubernetesObjectExtensions.MakeOwnerReference`
-- `KubeOps.Operator.Entities.Extensions.KubernetesObjectExtensions.WithOwnerReference`
-
-## Commands
-
-For convenience, there are multiple commands added to the executable
-of your operator (through the KubeOps package).
-
-Those are implemented with the [CommandLineUtils by NateMcMaster](https://github.com/natemcmaster/CommandLineUtils).
-
-you can see the help and overview when using
-`dotnet run -- --help` in your project. As you can see, you can run
-multiple commands. Some of them do install / uninstall your crds in
-your currently selected kubernetes cluster or can generate code.
-
-> For the normal "dotnet run" command exists a `--namespaced`
-> option that starts the operator in namespaced mode. This means
-> that only the given namespace is watched for entities.
-
-### Available Commands
-
-Here is a brief overview over the available commands:
-
-> all commands assume either the compiled dll or you using
-> `dotnet run -- ` as prepended command.
-
-- `""` (empty): runs the operator (normal `dotnet run`)
-- `version`: prints the version information for the actual connected kubernetes cluster
-- `install`: install the CRDs for the solution into the cluster
-- `uninstall`: uninstall the CRDs for the solution from the cluster
-- `generator`: entry command for generator commands (i.e. has subcommands), all commands
- output their result to the stdout or the given output path
- - `crd`: generate the CRDs
- - `docker`: generate the dockerfile
- - `installer`: generate the installer files (i.e. kustomization yaml) for the operator
- - `operator`: generate the deployment for the operator
- - `rbac`: generate the needed rbac roles / role bindings for the operator
-- `webhook`: entry command for webhook related operations
- - `install`: generate the server certificate and install the service / webhook registration
- - `register`: register the currently implemented webhooks to the currently selected cluster
-
-### Code Generation
-
-When installing this package, you also reference the default Targets and Props
-that come with the build engine. While building the following elements are generated:
-
-- Dockerfile (if not already present)
-- CRDs for your custom entities
-- RBAC roles and role bindings for your requested resources
-- Deployment files for your operator
-- Installation file for your operator (kustomize)
-
-The dockerfile will not be overwritten in case you have custom elements in there.
-The installation files won't be overwritten as well if you have custom elements in there.
-
-To regenerate those two elements, just delete them and rebuild your code.
-
-For the customization on those build targets, have a look at the next section.
-
-## MS Build extensions
-
-This project extends the default build process of dotnet with some
-code generation targets after the build.
-
-You'll find the configurations and targets here:
-
-- [KubeOps.targets](https://github.com/buehler/dotnet-operator-sdk/blob/master/src/KubeOps/Build/KubeOps.targets): defines the additional build targets
-
-They can be configured with the prop settings described below.
-The props file just defines the defaults.
-
-### Prop Settings
-
-You can overwrite the default behaviour of the building parts with the following
-variables that you can add in a `` in your `csproj` file:
-
-| Property | Description | Default Value |
-| ---------------------- | -------------------------------------------------------------------------- | ------------------------------- |
-| KubeOpsBasePath | Base path for all other elements | `$(MSBuildProjectDirectory)` |
-| KubeOpsDockerfilePath | The path of the dockerfile | `$(KubeOpsBasePath)\Dockerfile` |
-| KubeOpsDockerTag | Which dotnet sdk / run tag should be used | `latest` |
-| KubeOpsConfigRoot | The base directory for generated elements | `$(KubeOpsBasePath)\config` |
-| KubeOpsCrdDir | The directory for the generated crds | `$(KubeOpsConfigRoot)\crds` |
-| KubeOpsCrdFormat | Output format for crds | `Yaml` |
-| KubeOpsCrdUseOldCrds | Use V1Beta version of crd instead of V1
(for kubernetes version < 1.16) | `false` |
-| KubeOpsRbacDir | Where to put the roles | `$(KubeOpsConfigRoot)\rbac` |
-| KubeOpsRbacFormat | Output format for rbac | `Yaml` |
-| KubeOpsOperatorDir | Where to put operator related elements
(e.g. Deployment) | `$(KubeOpsConfigRoot)\operator` |
-| KubeOpsOperatorFormat | Output format for the operator | `Yaml` |
-| KubeOpsInstallerDir | Where to put the installation files
(e.g. Namespace / Kustomization) | `$(KubeOpsConfigRoot)\install` |
-| KubeOpsInstallerFormat | Output format for the installation files | `Yaml` |
-| KubeOpsSkipDockerfile | Skip dockerfile during build | `""` |
-| KubeOpsSkipCrds | Skip crd generation during build | `""` |
-| KubeOpsSkipRbac | Skip rbac generation during build | `""` |
-| KubeOpsSkipOperator | Skip operator generation during build | `""` |
-| KubeOpsSkipInstaller | Skip installer generation during build | `""` |
-
-## Advanced Topics
-
-### Assembly Scanning
-
-By default, KubeOps scans the assembly containing the main entrypoint for
-controller, finalizer, webhook and entity types, and automatically registers
-all types that implement the correct interfaces for usage.
-
-If some of the above are stored in a different assembly, KubeOps must be
-specifically instructed to scan that assembly `KubeOps.Operator.Builder.IOperatorBuilder.AddResourceAssembly` or else those types won't be loaded.
-
-```csharp
-public class Startup
-{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddKubernetesOperator()
- .AddResourceAssembly(typeof(CustomEntityController).Assembly)
- }
-
- public void Configure(IApplicationBuilder app)
- {
- app.UseKubernetesOperator();
- }
-}
-```
-
-### Manual Registration
-
-If desired, the default behavior of assembly scanning can be disabled so
-specific components can be registered manually. (Using both methods in parallel
-is supported, such as if you want to load all components from one assembly and
-only some from another.)
-
-See `KubeOps.Operator.Builder.IOperatorBuilder` for details on the methods
-utilized in this registration pattern.
-
-```csharp
-public class Startup
-{
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddKubernetesOperator(settings =>
- {
- settings.EnableAssemblyScanning = false;
- })
- .AddEntity()
- .AddController()
- .AddController()
- .AddFinalizer()
- .AddValidationWebhook()
- .AddMutationWebhook();
- }
-
- public void Configure(IApplicationBuilder app)
- {
- app.UseKubernetesOperator();
- }
-}
-```
diff --git a/_old/tests/KubeOps.Templates.Test/DotnetExecutor.cs b/_old/tests/KubeOps.Templates.Test/DotnetExecutor.cs
deleted file mode 100644
index 5e2844de..00000000
--- a/_old/tests/KubeOps.Templates.Test/DotnetExecutor.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Diagnostics;
-
-namespace KubeOps.Templates.Test;
-
-public abstract class DotnetExecutor
-{
- protected static async Task ExecuteDotnetProcess(string arguments)
- {
- var process = new Process { StartInfo = new() { FileName = "dotnet", Arguments = arguments, }, };
-
- process.Start();
- await process.WaitForExitAsync();
- }
-}
\ No newline at end of file
diff --git a/_old/tests/KubeOps.Templates.Test/KubeOps.Templates.Test.csproj b/_old/tests/KubeOps.Templates.Test/KubeOps.Templates.Test.csproj
deleted file mode 100644
index 52164163..00000000
--- a/_old/tests/KubeOps.Templates.Test/KubeOps.Templates.Test.csproj
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/_old/tests/KubeOps.Templates.Test/TemplateExecutor.cs b/_old/tests/KubeOps.Templates.Test/TemplateExecutor.cs
deleted file mode 100644
index 7a90c28a..00000000
--- a/_old/tests/KubeOps.Templates.Test/TemplateExecutor.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace KubeOps.Templates.Test;
-
-public class TemplateExecutor : DotnetExecutor, IDisposable
-{
- private string? _outputPath;
-
- public async Task ExecuteCSharpTemplate(string template, string? name = "Template")
- {
- _outputPath ??= Path.Join(Path.GetTempPath(), Path.GetRandomFileName());
- await ExecuteDotnetProcess(
- $"""new {template} -lang "C#" -n {name} -o {_outputPath} """);
- }
-
- public async Task ExecuteFSharpTemplate(string template, string? name = "Template")
- {
- _outputPath ??= Path.Join(Path.GetTempPath(), Path.GetRandomFileName());
- await ExecuteDotnetProcess(
- $"""new {template} -lang "F#" -n {name} -o {_outputPath} """);
- }
-
- public bool FileExists(params string[] name) =>
- _outputPath != null && File.Exists(Path.Join(_outputPath, Path.Combine(name)));
-
- public bool FileContains(string content, params string[] name)
- {
- var file = File.ReadAllText(Path.Join(_outputPath, Path.Combine(name)));
- return file.Contains(content);
- }
-
- public void Dispose()
- {
- if (_outputPath != null)
- {
- Directory.Delete(_outputPath, true);
- }
- }
-}
\ No newline at end of file
diff --git a/_old/tests/KubeOps.Templates.Test/TemplateInstaller.cs b/_old/tests/KubeOps.Templates.Test/TemplateInstaller.cs
deleted file mode 100644
index 2794641d..00000000
--- a/_old/tests/KubeOps.Templates.Test/TemplateInstaller.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace KubeOps.Templates.Test;
-
-public class TemplateInstaller : DotnetExecutor, IDisposable
-{
- private const string Up = "..";
-
- public TemplateInstaller()
- {
- ExecuteDotnetProcess($"new -i {TemplatesPath}").Wait();
- }
-
- private static string TemplatesPath => Path.GetFullPath(
- Path.Join(
- Directory.GetCurrentDirectory(),
- Up,
- Up,
- Up,
- Up,
- Up,
- "src",
- "KubeOps.Templates"));
-
- public void Dispose()
- {
- ExecuteDotnetProcess($"new -u {TemplatesPath}").Wait();
- }
-}
\ No newline at end of file
diff --git a/_old/tests/KubeOps.Templates.Test/Templates/Empty.CSharp.Test.cs b/_old/tests/KubeOps.Templates.Test/Templates/Empty.CSharp.Test.cs
deleted file mode 100644
index 3b6d9748..00000000
--- a/_old/tests/KubeOps.Templates.Test/Templates/Empty.CSharp.Test.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using FluentAssertions;
-
-using Xunit;
-
-namespace KubeOps.Templates.Test.Templates;
-
-[Collection("Template Tests")]
-public class EmptyCSharpTest : IDisposable
-{
- private readonly TemplateExecutor _executor = new();
-
- public EmptyCSharpTest(TemplateInstaller _)
- {
- }
-
- [Fact]
- public async Task Should_Create_Correct_Files()
- {
- await _executor.ExecuteCSharpTemplate("operator-empty");
- _executor.FileExists("Template.csproj").Should().BeTrue();
- _executor.FileExists("Program.cs").Should().BeTrue();
- _executor.FileExists("appsettings.Development.json").Should().BeTrue();
- _executor.FileExists("appsettings.json").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Add_KubeOps_Reference()
- {
- await _executor.ExecuteCSharpTemplate("operator-empty");
- _executor.FileContains("""
- PackageReference Include="KubeOps"
- """, "Template.csproj").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Add_KubeOps_Reference_Into_Program_Code()
- {
- await _executor.ExecuteCSharpTemplate("operator-empty");
- _executor.FileContains("builder.Services.AddKubernetesOperator();", "Program.cs").Should().BeTrue();
- _executor.FileContains("app.UseKubernetesOperator();", "Program.cs").Should().BeTrue();
- _executor.FileContains("await app.RunOperatorAsync(args);", "Program.cs").Should().BeTrue();
- }
-
- public void Dispose()
- {
- _executor.Dispose();
- }
-}
\ No newline at end of file
diff --git a/_old/tests/KubeOps.Templates.Test/Templates/Empty.FSharp.Test.cs b/_old/tests/KubeOps.Templates.Test/Templates/Empty.FSharp.Test.cs
deleted file mode 100644
index 7b415b4e..00000000
--- a/_old/tests/KubeOps.Templates.Test/Templates/Empty.FSharp.Test.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using FluentAssertions;
-
-using Xunit;
-
-namespace KubeOps.Templates.Test.Templates;
-
-[Collection("Template Tests")]
-public class EmptyFSharpTest : IDisposable
-{
- private readonly TemplateExecutor _executor = new();
-
- public EmptyFSharpTest(TemplateInstaller _)
- {
- }
-
- [Fact]
- public async Task Should_Create_Correct_Files()
- {
- await _executor.ExecuteFSharpTemplate("operator-empty");
- _executor.FileExists("Template.fsproj").Should().BeTrue();
- _executor.FileExists("Startup.fs").Should().BeTrue();
- _executor.FileExists("Program.fs").Should().BeTrue();
- _executor.FileExists("appsettings.Development.json").Should().BeTrue();
- _executor.FileExists("appsettings.json").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Add_KubeOps_Reference()
- {
- await _executor.ExecuteFSharpTemplate("operator-empty");
- _executor.FileContains("""
- PackageReference Include="KubeOps"
- """, "Template.fsproj").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Add_KubeOps_Reference_Into_Startup_Files()
- {
- await _executor.ExecuteFSharpTemplate("operator-empty");
- _executor.FileContains("services.AddKubernetesOperator() |> ignore", "Startup.fs").Should().BeTrue();
- _executor.FileContains("app.UseKubernetesOperator()", "Startup.fs").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Create_Correct_Program_Code()
- {
- await _executor.ExecuteFSharpTemplate("operator-empty");
- _executor.FileContains(".RunOperatorAsync args", "Program.fs")
- .Should()
- .BeTrue();
- }
-
- public void Dispose()
- {
- _executor.Dispose();
- }
-}
\ No newline at end of file
diff --git a/_old/tests/KubeOps.Templates.Test/Templates/Operator.CSharp.Test.cs b/_old/tests/KubeOps.Templates.Test/Templates/Operator.CSharp.Test.cs
deleted file mode 100644
index 86182f43..00000000
--- a/_old/tests/KubeOps.Templates.Test/Templates/Operator.CSharp.Test.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using FluentAssertions;
-
-using Xunit;
-
-namespace KubeOps.Templates.Test.Templates;
-
-[Collection("Template Tests")]
-public class OperatorCSharpTest : IDisposable
-{
- private readonly TemplateExecutor _executor = new();
-
- public OperatorCSharpTest(TemplateInstaller _)
- {
- }
-
- [Fact]
- public async Task Should_Create_Correct_Files()
- {
- await _executor.ExecuteCSharpTemplate("operator");
- _executor.FileExists("Template.csproj").Should().BeTrue();
- _executor.FileExists("Program.cs").Should().BeTrue();
- _executor.FileExists("appsettings.Development.json").Should().BeTrue();
- _executor.FileExists("appsettings.json").Should().BeTrue();
-
- _executor.FileExists("Controller", "DemoController.cs").Should().BeTrue();
- _executor.FileExists("Entities", "V1DemoEntity.cs").Should().BeTrue();
- _executor.FileExists("Finalizer", "DemoFinalizer.cs").Should().BeTrue();
- _executor.FileExists("Webhooks", "DemoValidator.cs").Should().BeTrue();
- _executor.FileExists("Webhooks", "DemoMutator.cs").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Add_KubeOps_Reference()
- {
- await _executor.ExecuteCSharpTemplate("operator");
- _executor.FileContains("""
- PackageReference Include="KubeOps"
- """, "Template.csproj").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Add_KubeOps_Reference_Into_Program_Code()
- {
- await _executor.ExecuteCSharpTemplate("operator");
- _executor.FileContains("builder.Services.AddKubernetesOperator();", "Program.cs").Should().BeTrue();
- _executor.FileContains("app.UseKubernetesOperator();", "Program.cs").Should().BeTrue();
- _executor.FileContains("await app.RunOperatorAsync(args);", "Program.cs").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Add_Correct_Demo_Files()
- {
- await _executor.ExecuteCSharpTemplate("operator");
-
- _executor.FileContains(
- "public class V1DemoEntity : CustomKubernetesEntity",
- "Entities",
- "V1DemoEntity.cs")
- .Should()
- .BeTrue();
- _executor.FileContains(
- "public class DemoController : IResourceController",
- "Controller",
- "DemoController.cs")
- .Should()
- .BeTrue();
- _executor.FileContains(
- "public class DemoFinalizer : IResourceFinalizer",
- "Finalizer",
- "DemoFinalizer.cs")
- .Should()
- .BeTrue();
- _executor.FileContains(
- "public class DemoValidator : IValidationWebhook",
- "Webhooks",
- "DemoValidator.cs")
- .Should()
- .BeTrue();
- _executor.FileContains(
- "public class DemoMutator : IMutationWebhook",
- "Webhooks",
- "DemoMutator.cs")
- .Should()
- .BeTrue();
- }
-
- public void Dispose()
- {
- _executor.Dispose();
- }
-}
\ No newline at end of file
diff --git a/_old/tests/KubeOps.Templates.Test/Templates/Operator.FSharp.Test.cs b/_old/tests/KubeOps.Templates.Test/Templates/Operator.FSharp.Test.cs
deleted file mode 100644
index fc64df01..00000000
--- a/_old/tests/KubeOps.Templates.Test/Templates/Operator.FSharp.Test.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using FluentAssertions;
-
-using Xunit;
-
-namespace KubeOps.Templates.Test.Templates;
-
-[Collection("Template Tests")]
-public class OperatorFSharpTest : IDisposable
-{
- private readonly TemplateExecutor _executor = new();
-
- public OperatorFSharpTest(TemplateInstaller _)
- {
- }
-
- [Fact]
- public async Task Should_Create_Correct_Files()
- {
- await _executor.ExecuteFSharpTemplate("operator");
- _executor.FileExists("Template.fsproj").Should().BeTrue();
- _executor.FileExists("Startup.fs").Should().BeTrue();
- _executor.FileExists("Program.fs").Should().BeTrue();
- _executor.FileExists("appsettings.Development.json").Should().BeTrue();
- _executor.FileExists("appsettings.json").Should().BeTrue();
-
- _executor.FileExists("Controller", "DemoController.fs").Should().BeTrue();
- _executor.FileExists("Entities", "V1DemoEntity.fs").Should().BeTrue();
- _executor.FileExists("Finalizer", "DemoFinalizer.fs").Should().BeTrue();
- _executor.FileExists("Webhooks", "DemoValidator.fs").Should().BeTrue();
- _executor.FileExists("Webhooks", "DemoMutator.fs").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Add_KubeOps_Reference()
- {
- await _executor.ExecuteFSharpTemplate("operator-empty");
- _executor.FileContains("""
- PackageReference Include="KubeOps"
- """, "Template.fsproj").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Add_KubeOps_Reference_Into_Startup_Files()
- {
- await _executor.ExecuteFSharpTemplate("operator-empty");
- _executor.FileContains("services.AddKubernetesOperator() |> ignore", "Startup.fs").Should().BeTrue();
- _executor.FileContains("app.UseKubernetesOperator()", "Startup.fs").Should().BeTrue();
- }
-
- [Fact]
- public async Task Should_Create_Correct_Program_Code()
- {
- await _executor.ExecuteFSharpTemplate("operator-empty");
- _executor.FileContains(".RunOperatorAsync args", "Program.fs")
- .Should()
- .BeTrue();
- }
-
- [Fact]
- public async Task Should_Add_Correct_Demo_Files()
- {
- await _executor.ExecuteFSharpTemplate("operator");
-
- _executor.FileContains(
- "inherit CustomKubernetesEntity()",
- "Entities",
- "V1DemoEntity.fs")
- .Should()
- .BeTrue();
- _executor.FileContains(
- "interface IResourceController with",
- "Controller",
- "DemoController.fs")
- .Should()
- .BeTrue();
- _executor.FileContains(
- "interface IResourceFinalizer with",
- "Finalizer",
- "DemoFinalizer.fs")
- .Should()
- .BeTrue();
- _executor.FileContains(
- "interface IValidationWebhook with",
- "Webhooks",
- "DemoValidator.fs")
- .Should()
- .BeTrue();
- _executor.FileContains(
- "interface IMutationWebhook with",
- "Webhooks",
- "DemoMutator.fs")
- .Should()
- .BeTrue();
- }
-
- public void Dispose()
- {
- _executor.Dispose();
- }
-}
\ No newline at end of file
diff --git a/_old/tests/KubeOps.Templates.Test/Templates/TemplateTestCollection.cs b/_old/tests/KubeOps.Templates.Test/Templates/TemplateTestCollection.cs
deleted file mode 100644
index 22ac0b59..00000000
--- a/_old/tests/KubeOps.Templates.Test/Templates/TemplateTestCollection.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using Xunit;
-
-namespace KubeOps.Templates.Test.Templates;
-
-[CollectionDefinition("Template Tests")]
-public class TemplateTestCollection : ICollectionFixture
-{
-}
\ No newline at end of file
diff --git a/examples/ConversionWebhookOperator/Controller/V1TestEntityController.cs b/examples/ConversionWebhookOperator/Controller/V1TestEntityController.cs
index c8380f17..7746041a 100644
--- a/examples/ConversionWebhookOperator/Controller/V1TestEntityController.cs
+++ b/examples/ConversionWebhookOperator/Controller/V1TestEntityController.cs
@@ -1,22 +1,22 @@
-using ConversionWebhookOperator.Entities;
-
-using KubeOps.Abstractions.Controller;
-using KubeOps.Abstractions.Rbac;
-
-namespace ConversionWebhookOperator.Controller;
-
-[EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
-public class V1TestEntityController(ILogger logger) : IEntityController
-{
- public Task ReconcileAsync(V1TestEntity entity)
- {
- logger.LogInformation("Reconciling entity {Entity}.", entity);
- return Task.CompletedTask;
- }
-
- public Task DeletedAsync(V1TestEntity entity)
- {
- logger.LogInformation("Deleted entity {Entity}.", entity);
- return Task.CompletedTask;
- }
-}
+using ConversionWebhookOperator.Entities;
+
+using KubeOps.Abstractions.Controller;
+using KubeOps.Abstractions.Rbac;
+
+namespace ConversionWebhookOperator.Controller;
+
+[EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
+public class V1TestEntityController(ILogger logger) : IEntityController
+{
+ public Task ReconcileAsync(V1TestEntity entity, CancellationToken cancellationToken)
+ {
+ logger.LogInformation("Reconciling entity {Entity}.", entity);
+ return Task.CompletedTask;
+ }
+
+ public Task DeletedAsync(V1TestEntity entity, CancellationToken cancellationToken)
+ {
+ logger.LogInformation("Deleted entity {Entity}.", entity);
+ return Task.CompletedTask;
+ }
+}
diff --git a/examples/ConversionWebhookOperator/Entities/V1TestEntity.cs b/examples/ConversionWebhookOperator/Entities/V1TestEntity.cs
index 90feac2c..a3c3d611 100644
--- a/examples/ConversionWebhookOperator/Entities/V1TestEntity.cs
+++ b/examples/ConversionWebhookOperator/Entities/V1TestEntity.cs
@@ -1,16 +1,16 @@
-using k8s.Models;
-
-using KubeOps.Abstractions.Entities;
-
-namespace ConversionWebhookOperator.Entities;
-
-[KubernetesEntity(Group = "conversionwebhook.dev", ApiVersion = "v1", Kind = "TestEntity")]
-public partial class V1TestEntity : CustomKubernetesEntity
-{
- public override string ToString() => $"Test Entity v1 ({Metadata.Name}): {Spec.Name}";
-
- public class EntitySpec
- {
- public string Name { get; set; } = string.Empty;
- }
-}
+using k8s.Models;
+
+using KubeOps.Abstractions.Entities;
+
+namespace ConversionWebhookOperator.Entities;
+
+[KubernetesEntity(Group = "conversionwebhook.dev", ApiVersion = "v1", Kind = "TestEntity")]
+public partial class V1TestEntity : CustomKubernetesEntity
+{
+ public override string ToString() => $"Test Entity v1 ({Metadata.Name}): {Spec.Name}";
+
+ public class EntitySpec
+ {
+ public string Name { get; set; } = string.Empty;
+ }
+}
diff --git a/examples/ConversionWebhookOperator/Entities/V2TestEntity.cs b/examples/ConversionWebhookOperator/Entities/V2TestEntity.cs
index 477c842e..8525bea5 100644
--- a/examples/ConversionWebhookOperator/Entities/V2TestEntity.cs
+++ b/examples/ConversionWebhookOperator/Entities/V2TestEntity.cs
@@ -1,18 +1,18 @@
-using k8s.Models;
-
-using KubeOps.Abstractions.Entities;
-
-namespace ConversionWebhookOperator.Entities;
-
-[KubernetesEntity(Group = "conversionwebhook.dev", ApiVersion = "v2", Kind = "TestEntity")]
-public partial class V2TestEntity : CustomKubernetesEntity
-{
- public override string ToString() => $"Test Entity v2 ({Metadata.Name}): {Spec.Firstname} {Spec.Lastname}";
-
- public class EntitySpec
- {
- public string Firstname { get; set; } = string.Empty;
-
- public string Lastname { get; set; } = string.Empty;
- }
-}
+using k8s.Models;
+
+using KubeOps.Abstractions.Entities;
+
+namespace ConversionWebhookOperator.Entities;
+
+[KubernetesEntity(Group = "conversionwebhook.dev", ApiVersion = "v2", Kind = "TestEntity")]
+public partial class V2TestEntity : CustomKubernetesEntity
+{
+ public override string ToString() => $"Test Entity v2 ({Metadata.Name}): {Spec.Firstname} {Spec.Lastname}";
+
+ public class EntitySpec
+ {
+ public string Firstname { get; set; } = string.Empty;
+
+ public string Lastname { get; set; } = string.Empty;
+ }
+}
diff --git a/examples/ConversionWebhookOperator/Entities/V3TestEntity.cs b/examples/ConversionWebhookOperator/Entities/V3TestEntity.cs
index cfcd8407..b4fef937 100644
--- a/examples/ConversionWebhookOperator/Entities/V3TestEntity.cs
+++ b/examples/ConversionWebhookOperator/Entities/V3TestEntity.cs
@@ -1,20 +1,20 @@
-using k8s.Models;
-
-using KubeOps.Abstractions.Entities;
-
-namespace ConversionWebhookOperator.Entities;
-
-[KubernetesEntity(Group = "conversionwebhook.dev", ApiVersion = "v3", Kind = "TestEntity")]
-public partial class V3TestEntity : CustomKubernetesEntity
-{
- public override string ToString() => $"Test Entity v3 ({Metadata.Name}): {Spec.Firstname} {Spec.MiddleName} {Spec.Lastname}";
-
- public class EntitySpec
- {
- public string Firstname { get; set; } = string.Empty;
-
- public string Lastname { get; set; } = string.Empty;
-
- public string? MiddleName { get; set; }
- }
-}
+using k8s.Models;
+
+using KubeOps.Abstractions.Entities;
+
+namespace ConversionWebhookOperator.Entities;
+
+[KubernetesEntity(Group = "conversionwebhook.dev", ApiVersion = "v3", Kind = "TestEntity")]
+public partial class V3TestEntity : CustomKubernetesEntity
+{
+ public override string ToString() => $"Test Entity v3 ({Metadata.Name}): {Spec.Firstname} {Spec.MiddleName} {Spec.Lastname}";
+
+ public class EntitySpec
+ {
+ public string Firstname { get; set; } = string.Empty;
+
+ public string Lastname { get; set; } = string.Empty;
+
+ public string? MiddleName { get; set; }
+ }
+}
diff --git a/examples/ConversionWebhookOperator/Program.cs b/examples/ConversionWebhookOperator/Program.cs
index cf328b30..44a63cea 100644
--- a/examples/ConversionWebhookOperator/Program.cs
+++ b/examples/ConversionWebhookOperator/Program.cs
@@ -1,22 +1,22 @@
-using KubeOps.Operator;
-using KubeOps.Operator.Web.Builder;
-
-var builder = WebApplication.CreateBuilder(args);
-builder.Services
- .AddKubernetesOperator()
- .RegisterComponents()
-#if DEBUG
- .AddDevelopmentTunnel(5000)
-#endif
- ;
-
-builder.Services
- .AddControllers();
-
-var app = builder.Build();
-
-app.UseRouting();
-app.UseDeveloperExceptionPage();
-app.MapControllers();
-
-await app.RunAsync();
+using KubeOps.Operator;
+using KubeOps.Operator.Web.Builder;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.Services
+ .AddKubernetesOperator()
+ .RegisterComponents()
+#if DEBUG
+ .AddDevelopmentTunnel(5000)
+#endif
+ ;
+
+builder.Services
+ .AddControllers();
+
+var app = builder.Build();
+
+app.UseRouting();
+app.UseDeveloperExceptionPage();
+app.MapControllers();
+
+await app.RunAsync();
diff --git a/examples/ConversionWebhookOperator/Webhooks/TestConversionWebhook.cs b/examples/ConversionWebhookOperator/Webhooks/TestConversionWebhook.cs
index 49b1bd65..086b7957 100644
--- a/examples/ConversionWebhookOperator/Webhooks/TestConversionWebhook.cs
+++ b/examples/ConversionWebhookOperator/Webhooks/TestConversionWebhook.cs
@@ -1,52 +1,52 @@
-using ConversionWebhookOperator.Entities;
-
-using KubeOps.Operator.Web.Webhooks.Conversion;
-
-namespace ConversionWebhookOperator.Webhooks;
-
-[ConversionWebhook(typeof(V3TestEntity))]
-public class TestConversionWebhook : ConversionWebhook
-{
- protected override IEnumerable> Converters => new IEntityConverter[]
- {
- new V1ToV3(), new V2ToV3(),
- };
-
- private class V1ToV3 : IEntityConverter
- {
- public V3TestEntity Convert(V1TestEntity from)
- {
- var nameSplit = from.Spec.Name.Split(' ');
- var result = new V3TestEntity { Metadata = from.Metadata };
- result.Spec.Firstname = nameSplit[0];
- result.Spec.Lastname = string.Join(' ', nameSplit[1..]);
- return result;
- }
-
- public V1TestEntity Revert(V3TestEntity to)
- {
- var result = new V1TestEntity { Metadata = to.Metadata };
- result.Spec.Name = $"{to.Spec.Firstname} {to.Spec.Lastname}";
- return result;
- }
- }
-
- private class V2ToV3 : IEntityConverter
- {
- public V3TestEntity Convert(V2TestEntity from)
- {
- var result = new V3TestEntity { Metadata = from.Metadata };
- result.Spec.Firstname = from.Spec.Firstname;
- result.Spec.Lastname = from.Spec.Lastname;
- return result;
- }
-
- public V2TestEntity Revert(V3TestEntity to)
- {
- var result = new V2TestEntity { Metadata = to.Metadata };
- result.Spec.Firstname = to.Spec.Firstname;
- result.Spec.Lastname = to.Spec.Lastname;
- return result;
- }
- }
-}
+using ConversionWebhookOperator.Entities;
+
+using KubeOps.Operator.Web.Webhooks.Conversion;
+
+namespace ConversionWebhookOperator.Webhooks;
+
+[ConversionWebhook(typeof(V3TestEntity))]
+public class TestConversionWebhook : ConversionWebhook
+{
+ protected override IEnumerable> Converters => new IEntityConverter[]
+ {
+ new V1ToV3(), new V2ToV3(),
+ };
+
+ private class V1ToV3 : IEntityConverter
+ {
+ public V3TestEntity Convert(V1TestEntity from)
+ {
+ var nameSplit = from.Spec.Name.Split(' ');
+ var result = new V3TestEntity { Metadata = from.Metadata };
+ result.Spec.Firstname = nameSplit[0];
+ result.Spec.Lastname = string.Join(' ', nameSplit[1..]);
+ return result;
+ }
+
+ public V1TestEntity Revert(V3TestEntity to)
+ {
+ var result = new V1TestEntity { Metadata = to.Metadata };
+ result.Spec.Name = $"{to.Spec.Firstname} {to.Spec.Lastname}";
+ return result;
+ }
+ }
+
+ private class V2ToV3 : IEntityConverter
+ {
+ public V3TestEntity Convert(V2TestEntity from)
+ {
+ var result = new V3TestEntity { Metadata = from.Metadata };
+ result.Spec.Firstname = from.Spec.Firstname;
+ result.Spec.Lastname = from.Spec.Lastname;
+ return result;
+ }
+
+ public V2TestEntity Revert(V3TestEntity to)
+ {
+ var result = new V2TestEntity { Metadata = to.Metadata };
+ result.Spec.Firstname = to.Spec.Firstname;
+ result.Spec.Lastname = to.Spec.Lastname;
+ return result;
+ }
+ }
+}
diff --git a/examples/Operator/Controller/V1TestEntityController.cs b/examples/Operator/Controller/V1TestEntityController.cs
index c7fb311b..d2c28cbf 100644
--- a/examples/Operator/Controller/V1TestEntityController.cs
+++ b/examples/Operator/Controller/V1TestEntityController.cs
@@ -1,32 +1,32 @@
-using KubeOps.Abstractions.Controller;
-using KubeOps.Abstractions.Events;
-using KubeOps.Abstractions.Queue;
-using KubeOps.Abstractions.Rbac;
-
-using Microsoft.Extensions.Logging;
-
-using Operator.Entities;
-
-namespace Operator.Controller;
-
-[EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
-public class V1TestEntityController(ILogger logger,
- EntityRequeue requeue,
- EventPublisher eventPublisher)
- : IEntityController
-{
- public async Task ReconcileAsync(V1TestEntity entity)
- {
- logger.LogInformation("Reconciling entity {Entity}.", entity);
-
- await eventPublisher(entity, "RECONCILED", "Entity was reconciled.");
-
- requeue(entity, TimeSpan.FromSeconds(5));
- }
-
- public Task DeletedAsync(V1TestEntity entity)
- {
- logger.LogInformation("Deleting entity {Entity}.", entity);
- return Task.CompletedTask;
- }
-}
+using KubeOps.Abstractions.Controller;
+using KubeOps.Abstractions.Events;
+using KubeOps.Abstractions.Queue;
+using KubeOps.Abstractions.Rbac;
+
+using Microsoft.Extensions.Logging;
+
+using Operator.Entities;
+
+namespace Operator.Controller;
+
+[EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
+public class V1TestEntityController(ILogger logger,
+ EntityRequeue requeue,
+ EventPublisher eventPublisher)
+ : IEntityController
+{
+ public async Task ReconcileAsync(V1TestEntity entity, CancellationToken cancellationToken)
+ {
+ logger.LogInformation("Reconciling entity {Entity}.", entity);
+
+ await eventPublisher(entity, "RECONCILED", "Entity was reconciled.");
+
+ requeue(entity, TimeSpan.FromSeconds(5));
+ }
+
+ public Task DeletedAsync(V1TestEntity entity, CancellationToken cancellationToken)
+ {
+ logger.LogInformation("Deleting entity {Entity}.", entity);
+ return Task.CompletedTask;
+ }
+}
diff --git a/examples/Operator/Entities/V1TestEntity.cs b/examples/Operator/Entities/V1TestEntity.cs
index 2ec912a7..b76650c1 100644
--- a/examples/Operator/Entities/V1TestEntity.cs
+++ b/examples/Operator/Entities/V1TestEntity.cs
@@ -1,18 +1,18 @@
-using k8s.Models;
-
-using KubeOps.Abstractions.Entities;
-
-namespace Operator.Entities;
-
-[KubernetesEntity(Group = "testing.dev", ApiVersion = "v1", Kind = "TestEntity")]
-public partial class V1TestEntity : CustomKubernetesEntity
-{
- public override string ToString() => $"Test Entity ({Metadata.Name}): {Spec.Username} ({Spec.Email})";
-
- public class EntitySpec
- {
- public string Username { get; set; } = string.Empty;
-
- public string Email { get; set; } = string.Empty;
- }
-}
+using k8s.Models;
+
+using KubeOps.Abstractions.Entities;
+
+namespace Operator.Entities;
+
+[KubernetesEntity(Group = "testing.dev", ApiVersion = "v1", Kind = "TestEntity")]
+public partial class V1TestEntity : CustomKubernetesEntity
+{
+ public override string ToString() => $"Test Entity ({Metadata.Name}): {Spec.Username} ({Spec.Email})";
+
+ public class EntitySpec
+ {
+ public string Username { get; set; } = string.Empty;
+
+ public string Email { get; set; } = string.Empty;
+ }
+}
diff --git a/examples/Operator/Finalizer/FinalizerOne.cs b/examples/Operator/Finalizer/FinalizerOne.cs
index f46a173f..319bbabf 100644
--- a/examples/Operator/Finalizer/FinalizerOne.cs
+++ b/examples/Operator/Finalizer/FinalizerOne.cs
@@ -1,13 +1,13 @@
-using KubeOps.Abstractions.Finalizer;
-
-using Operator.Entities;
-
-namespace Operator.Finalizer;
-
-public class FinalizerOne : IEntityFinalizer
-{
- public Task FinalizeAsync(V1TestEntity entity)
- {
- return Task.CompletedTask;
- }
-}
+using KubeOps.Abstractions.Finalizer;
+
+using Operator.Entities;
+
+namespace Operator.Finalizer;
+
+public class FinalizerOne : IEntityFinalizer
+{
+ public Task FinalizeAsync(V1TestEntity entity, CancellationToken cancellationToken)
+ {
+ return Task.CompletedTask;
+ }
+}
diff --git a/examples/Operator/Program.cs b/examples/Operator/Program.cs
index 0bd007a9..ba2acd6a 100644
--- a/examples/Operator/Program.cs
+++ b/examples/Operator/Program.cs
@@ -1,15 +1,15 @@
-using KubeOps.Operator;
-
-using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
-
-var builder = Host.CreateApplicationBuilder(args);
-
-builder.Logging.SetMinimumLevel(LogLevel.Trace);
-
-builder.Services
- .AddKubernetesOperator()
- .RegisterComponents();
-
-using var host = builder.Build();
-await host.RunAsync();
+using KubeOps.Operator;
+
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+var builder = Host.CreateApplicationBuilder(args);
+
+builder.Logging.SetMinimumLevel(LogLevel.Trace);
+
+builder.Services
+ .AddKubernetesOperator()
+ .RegisterComponents();
+
+using var host = builder.Build();
+await host.RunAsync();
diff --git a/examples/WebhookOperator/Controller/V1TestEntityController.cs b/examples/WebhookOperator/Controller/V1TestEntityController.cs
index 5fff076f..c511b7a7 100644
--- a/examples/WebhookOperator/Controller/V1TestEntityController.cs
+++ b/examples/WebhookOperator/Controller/V1TestEntityController.cs
@@ -1,22 +1,22 @@
-using KubeOps.Abstractions.Controller;
-using KubeOps.Abstractions.Rbac;
-
-using WebhookOperator.Entities;
-
-namespace WebhookOperator.Controller;
-
-[EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
-public class V1TestEntityController(ILogger logger) : IEntityController
-{
- public Task ReconcileAsync(V1TestEntity entity)
- {
- logger.LogInformation("Reconciling entity {Entity}.", entity);
- return Task.CompletedTask;
- }
-
- public Task DeletedAsync(V1TestEntity entity)
- {
- logger.LogInformation("Deleted entity {Entity}.", entity);
- return Task.CompletedTask;
- }
-}
+using KubeOps.Abstractions.Controller;
+using KubeOps.Abstractions.Rbac;
+
+using WebhookOperator.Entities;
+
+namespace WebhookOperator.Controller;
+
+[EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
+public class V1TestEntityController(ILogger logger) : IEntityController
+{
+ public Task ReconcileAsync(V1TestEntity entity, CancellationToken cancellationToken)
+ {
+ logger.LogInformation("Reconciling entity {Entity}.", entity);
+ return Task.CompletedTask;
+ }
+
+ public Task DeletedAsync(V1TestEntity entity, CancellationToken cancellationToken)
+ {
+ logger.LogInformation("Deleted entity {Entity}.", entity);
+ return Task.CompletedTask;
+ }
+}
diff --git a/examples/WebhookOperator/Entities/V1TestEntity.cs b/examples/WebhookOperator/Entities/V1TestEntity.cs
index 7855d56b..8610df26 100644
--- a/examples/WebhookOperator/Entities/V1TestEntity.cs
+++ b/examples/WebhookOperator/Entities/V1TestEntity.cs
@@ -1,16 +1,16 @@
-using k8s.Models;
-
-using KubeOps.Abstractions.Entities;
-
-namespace WebhookOperator.Entities;
-
-[KubernetesEntity(Group = "webhook.dev", ApiVersion = "v1", Kind = "TestEntity")]
-public partial class V1TestEntity : CustomKubernetesEntity
-{
- public override string ToString() => $"Test Entity ({Metadata.Name}): {Spec.Username}";
-
- public class EntitySpec
- {
- public string Username { get; set; } = string.Empty;
- }
-}
+using k8s.Models;
+
+using KubeOps.Abstractions.Entities;
+
+namespace WebhookOperator.Entities;
+
+[KubernetesEntity(Group = "webhook.dev", ApiVersion = "v1", Kind = "TestEntity")]
+public partial class V1TestEntity : CustomKubernetesEntity
+{
+ public override string ToString() => $"Test Entity ({Metadata.Name}): {Spec.Username}";
+
+ public class EntitySpec
+ {
+ public string Username { get; set; } = string.Empty;
+ }
+}
diff --git a/examples/WebhookOperator/Program.cs b/examples/WebhookOperator/Program.cs
index cf328b30..44a63cea 100644
--- a/examples/WebhookOperator/Program.cs
+++ b/examples/WebhookOperator/Program.cs
@@ -1,22 +1,22 @@
-using KubeOps.Operator;
-using KubeOps.Operator.Web.Builder;
-
-var builder = WebApplication.CreateBuilder(args);
-builder.Services
- .AddKubernetesOperator()
- .RegisterComponents()
-#if DEBUG
- .AddDevelopmentTunnel(5000)
-#endif
- ;
-
-builder.Services
- .AddControllers();
-
-var app = builder.Build();
-
-app.UseRouting();
-app.UseDeveloperExceptionPage();
-app.MapControllers();
-
-await app.RunAsync();
+using KubeOps.Operator;
+using KubeOps.Operator.Web.Builder;
+
+var builder = WebApplication.CreateBuilder(args);
+builder.Services
+ .AddKubernetesOperator()
+ .RegisterComponents()
+#if DEBUG
+ .AddDevelopmentTunnel(5000)
+#endif
+ ;
+
+builder.Services
+ .AddControllers();
+
+var app = builder.Build();
+
+app.UseRouting();
+app.UseDeveloperExceptionPage();
+app.MapControllers();
+
+await app.RunAsync();
diff --git a/examples/WebhookOperator/Webhooks/TestMutationWebhook.cs b/examples/WebhookOperator/Webhooks/TestMutationWebhook.cs
index e1e2034e..c017c486 100644
--- a/examples/WebhookOperator/Webhooks/TestMutationWebhook.cs
+++ b/examples/WebhookOperator/Webhooks/TestMutationWebhook.cs
@@ -1,20 +1,20 @@
-using KubeOps.Operator.Web.Webhooks.Admission.Mutation;
-
-using WebhookOperator.Entities;
-
-namespace WebhookOperator.Webhooks;
-
-[MutationWebhook(typeof(V1TestEntity))]
-public class TestMutationWebhook : MutationWebhook
-{
- public override MutationResult Create(V1TestEntity entity, bool dryRun)
- {
- if (entity.Spec.Username == "overwrite")
- {
- entity.Spec.Username = "random overwritten";
- return Modified(entity);
- }
-
- return NoChanges();
- }
-}
+using KubeOps.Operator.Web.Webhooks.Admission.Mutation;
+
+using WebhookOperator.Entities;
+
+namespace WebhookOperator.Webhooks;
+
+[MutationWebhook(typeof(V1TestEntity))]
+public class TestMutationWebhook : MutationWebhook
+{
+ public override MutationResult Create(V1TestEntity entity, bool dryRun)
+ {
+ if (entity.Spec.Username == "overwrite")
+ {
+ entity.Spec.Username = "random overwritten";
+ return Modified(entity);
+ }
+
+ return NoChanges();
+ }
+}
diff --git a/examples/WebhookOperator/Webhooks/TestValidationWebhook.cs b/examples/WebhookOperator/Webhooks/TestValidationWebhook.cs
index 0422c552..c1c1d53d 100644
--- a/examples/WebhookOperator/Webhooks/TestValidationWebhook.cs
+++ b/examples/WebhookOperator/Webhooks/TestValidationWebhook.cs
@@ -1,29 +1,29 @@
-using KubeOps.Operator.Web.Webhooks.Admission.Validation;
-
-using WebhookOperator.Entities;
-
-namespace WebhookOperator.Webhooks;
-
-[ValidationWebhook(typeof(V1TestEntity))]
-public class TestValidationWebhook : ValidationWebhook
-{
- public override ValidationResult Create(V1TestEntity entity, bool dryRun)
- {
- if (entity.Spec.Username == "forbidden")
- {
- return Fail("name may not be 'forbidden'.", 422);
- }
-
- return Success();
- }
-
- public override ValidationResult Update(V1TestEntity oldEntity, V1TestEntity newEntity, bool dryRun)
- {
- if (newEntity.Spec.Username == "forbidden")
- {
- return Fail("name may not be 'forbidden'.");
- }
-
- return Success();
- }
-}
+using KubeOps.Operator.Web.Webhooks.Admission.Validation;
+
+using WebhookOperator.Entities;
+
+namespace WebhookOperator.Webhooks;
+
+[ValidationWebhook(typeof(V1TestEntity))]
+public class TestValidationWebhook : ValidationWebhook
+{
+ public override ValidationResult Create(V1TestEntity entity, bool dryRun)
+ {
+ if (entity.Spec.Username == "forbidden")
+ {
+ return Fail("name may not be 'forbidden'.", 422);
+ }
+
+ return Success();
+ }
+
+ public override ValidationResult Update(V1TestEntity oldEntity, V1TestEntity newEntity, bool dryRun)
+ {
+ if (newEntity.Spec.Username == "forbidden")
+ {
+ return Fail("name may not be 'forbidden'.");
+ }
+
+ return Success();
+ }
+}
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 3b03b377..edd3521c 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -41,10 +41,10 @@
Condition="$(MSBuildProjectExtension) == '.csproj'" />
-
+
diff --git a/src/KubeOps.Abstractions/Builder/IOperatorBuilder.cs b/src/KubeOps.Abstractions/Builder/IOperatorBuilder.cs
index 2553ffd9..0088276c 100644
--- a/src/KubeOps.Abstractions/Builder/IOperatorBuilder.cs
+++ b/src/KubeOps.Abstractions/Builder/IOperatorBuilder.cs
@@ -1,49 +1,49 @@
-using k8s;
-using k8s.Models;
-
-using KubeOps.Abstractions.Controller;
-using KubeOps.Abstractions.Finalizer;
-
-using Microsoft.Extensions.DependencyInjection;
-
-namespace KubeOps.Abstractions.Builder;
-
-///
-/// KubeOps operator builder.
-///
-public interface IOperatorBuilder
-{
- ///
- /// The original service collection.
- ///
- IServiceCollection Services { get; }
-
- ///
- /// Add a controller implementation for a specific entity to the operator.
- /// The metadata for the entity must be added as well.
- ///
- /// Implementation type of the controller.
- /// Entity type.
- /// The builder for chaining.
- IOperatorBuilder AddController()
- where TImplementation : class, IEntityController
- where TEntity : IKubernetesObject;
-
- ///
- /// Add a finalizer implementation for a specific entity.
- /// This adds the implementation as a transient service and registers
- /// the finalizer with the provided identifier. Then an
- /// is registered to
- /// provide a delegate for attaching the finalizer to an entity.
- ///
- ///
- /// The identifier for the finalizer.
- /// This string is added to the Kubernetes entity as a finalizer.
- ///
- /// Type of the finalizer implementation.
- /// Type of the Kubernetes entity.
- /// The builder for chaining.
- IOperatorBuilder AddFinalizer(string identifier)
- where TImplementation : class, IEntityFinalizer
- where TEntity : IKubernetesObject;
-}
+using k8s;
+using k8s.Models;
+
+using KubeOps.Abstractions.Controller;
+using KubeOps.Abstractions.Finalizer;
+
+using Microsoft.Extensions.DependencyInjection;
+
+namespace KubeOps.Abstractions.Builder;
+
+///
+/// KubeOps operator builder.
+///
+public interface IOperatorBuilder
+{
+ ///
+ /// The original service collection.
+ ///
+ IServiceCollection Services { get; }
+
+ ///
+ /// Add a controller implementation for a specific entity to the operator.
+ /// The metadata for the entity must be added as well.
+ ///
+ /// Implementation type of the controller.
+ /// Entity type.
+ /// The builder for chaining.
+ IOperatorBuilder AddController()
+ where TImplementation : class, IEntityController
+ where TEntity : IKubernetesObject;
+
+ ///
+ /// Add a finalizer implementation for a specific entity.
+ /// This adds the implementation as a transient service and registers
+ /// the finalizer with the provided identifier. Then an
+ /// is registered to
+ /// provide a delegate for attaching the finalizer to an entity.
+ ///
+ ///
+ /// The identifier for the finalizer.
+ /// This string is added to the Kubernetes entity as a finalizer.
+ ///
+ /// Type of the finalizer implementation.
+ /// Type of the Kubernetes entity.
+ /// The builder for chaining.
+ IOperatorBuilder AddFinalizer(string identifier)
+ where TImplementation : class, IEntityFinalizer
+ where TEntity : IKubernetesObject;
+}
diff --git a/src/KubeOps.Abstractions/Builder/OperatorSettings.cs b/src/KubeOps.Abstractions/Builder/OperatorSettings.cs
index ac110659..2faed930 100644
--- a/src/KubeOps.Abstractions/Builder/OperatorSettings.cs
+++ b/src/KubeOps.Abstractions/Builder/OperatorSettings.cs
@@ -1,62 +1,62 @@
-using System.Text.RegularExpressions;
-
-namespace KubeOps.Abstractions.Builder;
-
-///
-/// Operator settings.
-///
-public sealed class OperatorSettings
-{
- private const string DefaultOperatorName = "KubernetesOperator";
- private const string NonCharReplacement = "-";
-
- ///
- /// The name of the operator that appears in logs and other elements.
- /// Defaults to "kubernetesoperator" when not set.
- ///
- public string Name { get; set; } =
- new Regex(@"(\W|_)", RegexOptions.CultureInvariant).Replace(
- DefaultOperatorName,
- NonCharReplacement)
- .ToLowerInvariant();
-
- ///
- ///
- /// Controls the namespace which is watched by the operator.
- /// If this field is left `null`, all namespaces are watched for
- /// CRD instances.
- ///
- ///
- public string? Namespace { get; set; }
-
- ///
- ///
- /// Whether the leader elector should run. You should enable
- /// this if you plan to run the operator redundantly.
- ///
- ///
- /// If this is disabled and an operator runs in multiple instances
- /// (in the same namespace), it can lead to a "split brain" problem.
- ///
- ///
- /// Defaults to `false`.
- ///
- ///
- public bool EnableLeaderElection { get; set; } = false;
-
- ///
- /// Defines how long one lease is valid for any leader.
- /// Defaults to 15 seconds.
- ///
- public TimeSpan LeaderElectionLeaseDuration { get; set; } = TimeSpan.FromSeconds(15);
-
- ///
- /// When the leader elector tries to refresh the leadership lease.
- ///
- public TimeSpan LeaderElectionRenewDeadline { get; set; } = TimeSpan.FromSeconds(10);
-
- ///
- /// The wait timeout if the lease cannot be acquired.
- ///
- public TimeSpan LeaderElectionRetryPeriod { get; set; } = TimeSpan.FromSeconds(2);
-}
+using System.Text.RegularExpressions;
+
+namespace KubeOps.Abstractions.Builder;
+
+///
+/// Operator settings.
+///
+public sealed class OperatorSettings
+{
+ private const string DefaultOperatorName = "KubernetesOperator";
+ private const string NonCharReplacement = "-";
+
+ ///
+ /// The name of the operator that appears in logs and other elements.
+ /// Defaults to "kubernetesoperator" when not set.
+ ///
+ public string Name { get; set; } =
+ new Regex(@"(\W|_)", RegexOptions.CultureInvariant).Replace(
+ DefaultOperatorName,
+ NonCharReplacement)
+ .ToLowerInvariant();
+
+ ///
+ ///
+ /// Controls the namespace which is watched by the operator.
+ /// If this field is left `null`, all namespaces are watched for
+ /// CRD instances.
+ ///
+ ///
+ public string? Namespace { get; set; }
+
+ ///
+ ///
+ /// Whether the leader elector should run. You should enable
+ /// this if you plan to run the operator redundantly.
+ ///
+ ///
+ /// If this is disabled and an operator runs in multiple instances
+ /// (in the same namespace), it can lead to a "split brain" problem.
+ ///
+ ///
+ /// Defaults to `false`.
+ ///
+ ///
+ public bool EnableLeaderElection { get; set; } = false;
+
+ ///
+ /// Defines how long one lease is valid for any leader.
+ /// Defaults to 15 seconds.
+ ///
+ public TimeSpan LeaderElectionLeaseDuration { get; set; } = TimeSpan.FromSeconds(15);
+
+ ///
+ /// When the leader elector tries to refresh the leadership lease.
+ ///
+ public TimeSpan LeaderElectionRenewDeadline { get; set; } = TimeSpan.FromSeconds(10);
+
+ ///
+ /// The wait timeout if the lease cannot be acquired.
+ ///
+ public TimeSpan LeaderElectionRetryPeriod { get; set; } = TimeSpan.FromSeconds(2);
+}
diff --git a/src/KubeOps.Abstractions/Controller/IEntityController{TEntity}.cs b/src/KubeOps.Abstractions/Controller/IEntityController{TEntity}.cs
index 7d3c3892..87856087 100644
--- a/src/KubeOps.Abstractions/Controller/IEntityController{TEntity}.cs
+++ b/src/KubeOps.Abstractions/Controller/IEntityController{TEntity}.cs
@@ -1,56 +1,56 @@
-using k8s;
-using k8s.Models;
-
-namespace KubeOps.Abstractions.Controller;
-
-///
-/// Generic entity controller. The controller manages the reconcile loop
-/// for a given entity type.
-///
-/// The type of the Kubernetes entity.
-///
-/// Simple example controller that just logs the entity.
-///
-/// public class V1TestEntityController : IEntityController<V1TestEntity>
-/// {
-/// private readonly ILogger<V1TestEntityController> _logger;
-///
-/// public V1TestEntityController(
-/// ILogger<V1TestEntityController> logger)
-/// {
-/// _logger = logger;
-/// }
-///
-/// public async Task ReconcileAsync(V1TestEntity entity)
-/// {
-/// _logger.LogInformation("Reconciling entity {Entity}.", entity);
-/// }
-///
-/// public async Task DeletedAsync(V1TestEntity entity)
-/// {
-/// _logger.LogInformation("Deleting entity {Entity}.", entity);
-/// }
-/// }
-///
-///
-public interface IEntityController
- where TEntity : IKubernetesObject
-{
- ///
- /// Called for `added` and `modified` events from the watcher.
- ///
- /// The entity that fired the reconcile event.
- /// A task that completes when the reconciliation is done.
- Task ReconcileAsync(TEntity entity) =>
- Task.CompletedTask;
-
- ///
- /// Called for `delete` events for a given entity.
- ///
- /// The entity that fired the deleted event.
- ///
- /// A task that completes, when the reconciliation is done.
- ///
- Task DeletedAsync(TEntity entity) =>
- Task.CompletedTask;
-}
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Controller;
+
+///
+/// Generic entity controller. The controller manages the reconcile loop
+/// for a given entity type.
+///
+/// The type of the Kubernetes entity.
+///
+/// Simple example controller that just logs the entity.
+///
+/// public class V1TestEntityController : IEntityController<V1TestEntity>
+/// {
+/// private readonly ILogger<V1TestEntityController> _logger;
+///
+/// public V1TestEntityController(
+/// ILogger<V1TestEntityController> logger)
+/// {
+/// _logger = logger;
+/// }
+///
+/// public async Task ReconcileAsync(V1TestEntity entity, CancellationToken token)
+/// {
+/// _logger.LogInformation("Reconciling entity {Entity}.", entity);
+/// }
+///
+/// public async Task DeletedAsync(V1TestEntity entity, CancellationToken token)
+/// {
+/// _logger.LogInformation("Deleting entity {Entity}.", entity);
+/// }
+/// }
+///
+///
+public interface IEntityController
+ where TEntity : IKubernetesObject
+{
+ ///
+ /// Called for `added` and `modified` events from the watcher.
+ ///
+ /// The entity that fired the reconcile event.
+ /// The token to monitor for cancellation requests.
+ /// A task that completes when the reconciliation is done.
+ Task ReconcileAsync(TEntity entity, CancellationToken cancellationToken);
+
+ ///
+ /// Called for `delete` events for a given entity.
+ ///
+ /// The entity that fired the deleted event.
+ /// The token to monitor for cancellation requests.
+ ///
+ /// A task that completes, when the reconciliation is done.
+ ///
+ Task DeletedAsync(TEntity entity, CancellationToken cancellationToken);
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/AdditionalPrinterColumnAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/AdditionalPrinterColumnAttribute.cs
index b561fdcd..286565bd 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/AdditionalPrinterColumnAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/AdditionalPrinterColumnAttribute.cs
@@ -1,30 +1,30 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines a property as an additional printer column.
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class AdditionalPrinterColumnAttribute(PrinterColumnPriority priority = default, string? name = null)
- : Attribute
-{
- ///
- /// The name of the column. Defaults to the property-name.
- ///
- public string? Name => name;
-
- ///
- /// The priority of the additional printer column.
- /// As documented in
- /// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#priority
- /// the following rules apply to priority:
- ///
- /// -
- /// Columns with priority `0` are shown in standard view
- ///
- /// -
- /// Columns with priority greater than `0` are shown only in wide view
- ///
- ///
- ///
- public PrinterColumnPriority Priority => priority;
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines a property as an additional printer column.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class AdditionalPrinterColumnAttribute(PrinterColumnPriority priority = default, string? name = null)
+ : Attribute
+{
+ ///
+ /// The name of the column. Defaults to the property-name.
+ ///
+ public string? Name => name;
+
+ ///
+ /// The priority of the additional printer column.
+ /// As documented in
+ /// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#priority
+ /// the following rules apply to priority:
+ ///
+ /// -
+ /// Columns with priority `0` are shown in standard view
+ ///
+ /// -
+ /// Columns with priority greater than `0` are shown only in wide view
+ ///
+ ///
+ ///
+ public PrinterColumnPriority Priority => priority;
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/DescriptionAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/DescriptionAttribute.cs
index 9db44fef..3095000c 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/DescriptionAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/DescriptionAttribute.cs
@@ -1,14 +1,14 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines a description for a property. This precedes the description found in a
-/// XML documentation file.
-///
-[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
-public class DescriptionAttribute(string description) : Attribute
-{
- ///
- /// The given description for the property.
- ///
- public string Description => description;
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines a description for a property. This precedes the description found in a
+/// XML documentation file.
+///
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
+public class DescriptionAttribute(string description) : Attribute
+{
+ ///
+ /// The given description for the property.
+ ///
+ public string Description => description;
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/EmbeddedResourceAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/EmbeddedResourceAttribute.cs
index 60e6818e..b09ddecb 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/EmbeddedResourceAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/EmbeddedResourceAttribute.cs
@@ -1,12 +1,12 @@
-using k8s.Models;
-
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines a property as an embedded resource.
-/// This property can contain another Kubernetes object
-/// (e.g. a or a ).
-/// This implicitly sets the .
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class EmbeddedResourceAttribute : Attribute;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines a property as an embedded resource.
+/// This property can contain another Kubernetes object
+/// (e.g. a or a ).
+/// This implicitly sets the .
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class EmbeddedResourceAttribute : Attribute;
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/EntityScopeAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/EntityScopeAttribute.cs
index 7468850e..08ecfb83 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/EntityScopeAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/EntityScopeAttribute.cs
@@ -1,7 +1,7 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-[AttributeUsage(AttributeTargets.Class, Inherited = false)]
-public class EntityScopeAttribute(EntityScope scope = default) : Attribute
-{
- public EntityScope Scope => scope;
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+[AttributeUsage(AttributeTargets.Class, Inherited = false)]
+public class EntityScopeAttribute(EntityScope scope = default) : Attribute
+{
+ public EntityScope Scope => scope;
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/ExternalDocsAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/ExternalDocsAttribute.cs
index 237b0f35..864c9488 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/ExternalDocsAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/ExternalDocsAttribute.cs
@@ -1,18 +1,18 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines that the property has an external documentation.
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class ExternalDocsAttribute(string url, string? description = null) : Attribute
-{
- ///
- /// Additional description.
- ///
- public string? Description => description;
-
- ///
- /// Url where to find the documentation.
- ///
- public string Url => url;
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines that the property has an external documentation.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class ExternalDocsAttribute(string url, string? description = null) : Attribute
+{
+ ///
+ /// Additional description.
+ ///
+ public string? Description => description;
+
+ ///
+ /// Url where to find the documentation.
+ ///
+ public string Url => url;
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/GenericAdditionalPrinterColumnAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/GenericAdditionalPrinterColumnAttribute.cs
index fc39dabb..912bd164 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/GenericAdditionalPrinterColumnAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/GenericAdditionalPrinterColumnAttribute.cs
@@ -1,118 +1,118 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines a generic additional printer column.
-/// With this, other elements (such as Metadata.Name)
-/// can be referenced. In contrast to the ,
-/// all needed information must be provided.
-///
-[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-public class GenericAdditionalPrinterColumnAttribute : Attribute
-{
- ///
- /// Create a generic additional printer column.
- ///
- /// JsonPath as in .
- /// Name as in .
- /// Type as in .
- public GenericAdditionalPrinterColumnAttribute(string jsonPath, string name, string type)
- {
- JsonPath = jsonPath;
- Name = name;
- Type = type;
- }
-
- ///
- /// The json path for the property inside the resource.
- /// .spec.replicas
- /// .metadata.namespace
- /// .metadata.creationTimestamp
- ///
- public string JsonPath { get; }
-
- ///
- /// The name of the column.
- ///
- public string Name { get; }
-
- ///
- /// Description for the column.
- ///
- public string? Description { get; init; }
-
- ///
- /// The type of the column.
- /// As documented in
- /// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#type.
- /// The type field can be any of the following (from OpenAPI v3 data types):
- ///
- /// -
- /// `integer` - non-floating-point number
- ///
- /// -
- /// `number` - floating point number
- ///
- /// -
- /// `string` - strings
- ///
- /// -
- /// `boolean`- `true` or `false`
- ///
- /// -
- /// `date` - rendered differentially as time since this timestamp
- ///
- ///
- /// If the value inside a CustomResource does not match the type specified for the column, the value is omitted.
- ///
- public string Type { get; }
-
- ///
- /// The format of the column.
- /// As documented in
- /// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#format.
- /// The format field can be any of the following:
- ///
- /// -
- /// `int32`
- ///
- /// -
- /// `int64`
- ///
- /// -
- /// `float`
- ///
- /// -
- /// `double`
- ///
- /// -
- /// `byte`
- ///
- /// -
- /// `date`
- ///
- /// -
- /// `date-time`
- ///
- /// -
- /// `password`
- ///
- ///
- ///
- public string? Format { get; init; }
-
- ///
- /// The priority of the additional printer column.
- /// As documented in
- /// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#priority
- /// the following rules apply to priority:
- ///
- /// -
- /// Columns with priority `0` are shown in standard view
- ///
- /// -
- /// Columns with priority greater than `0` are shown only in wide view
- ///
- ///
- ///
- public PrinterColumnPriority Priority { get; init; }
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines a generic additional printer column.
+/// With this, other elements (such as Metadata.Name)
+/// can be referenced. In contrast to the ,
+/// all needed information must be provided.
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public class GenericAdditionalPrinterColumnAttribute : Attribute
+{
+ ///
+ /// Create a generic additional printer column.
+ ///
+ /// JsonPath as in .
+ /// Name as in .
+ /// Type as in .
+ public GenericAdditionalPrinterColumnAttribute(string jsonPath, string name, string type)
+ {
+ JsonPath = jsonPath;
+ Name = name;
+ Type = type;
+ }
+
+ ///
+ /// The json path for the property inside the resource.
+ /// .spec.replicas
+ /// .metadata.namespace
+ /// .metadata.creationTimestamp
+ ///
+ public string JsonPath { get; }
+
+ ///
+ /// The name of the column.
+ ///
+ public string Name { get; }
+
+ ///
+ /// Description for the column.
+ ///
+ public string? Description { get; init; }
+
+ ///
+ /// The type of the column.
+ /// As documented in
+ /// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#type.
+ /// The type field can be any of the following (from OpenAPI v3 data types):
+ ///
+ /// -
+ /// `integer` - non-floating-point number
+ ///
+ /// -
+ /// `number` - floating point number
+ ///
+ /// -
+ /// `string` - strings
+ ///
+ /// -
+ /// `boolean`- `true` or `false`
+ ///
+ /// -
+ /// `date` - rendered differentially as time since this timestamp
+ ///
+ ///
+ /// If the value inside a CustomResource does not match the type specified for the column, the value is omitted.
+ ///
+ public string Type { get; }
+
+ ///
+ /// The format of the column.
+ /// As documented in
+ /// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#format.
+ /// The format field can be any of the following:
+ ///
+ /// -
+ /// `int32`
+ ///
+ /// -
+ /// `int64`
+ ///
+ /// -
+ /// `float`
+ ///
+ /// -
+ /// `double`
+ ///
+ /// -
+ /// `byte`
+ ///
+ /// -
+ /// `date`
+ ///
+ /// -
+ /// `date-time`
+ ///
+ /// -
+ /// `password`
+ ///
+ ///
+ ///
+ public string? Format { get; init; }
+
+ ///
+ /// The priority of the additional printer column.
+ /// As documented in
+ /// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#priority
+ /// the following rules apply to priority:
+ ///
+ /// -
+ /// Columns with priority `0` are shown in standard view
+ ///
+ /// -
+ /// Columns with priority greater than `0` are shown only in wide view
+ ///
+ ///
+ ///
+ public PrinterColumnPriority Priority { get; init; }
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/IgnoreEntityAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/IgnoreEntityAttribute.cs
index f55bf314..fc16b717 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/IgnoreEntityAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/IgnoreEntityAttribute.cs
@@ -1,8 +1,8 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Attribute that states that the given entity or property should be
-/// ignored during CRD generation.
-///
-[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
-public class IgnoreAttribute : Attribute;
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Attribute that states that the given entity or property should be
+/// ignored during CRD generation.
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
+public class IgnoreAttribute : Attribute;
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/ItemsAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/ItemsAttribute.cs
index 5253044c..7faeb71d 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/ItemsAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/ItemsAttribute.cs
@@ -1,26 +1,26 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Define minimum and maximum items count for an array property.
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class ItemsAttribute(long minItems = -1, long maxItems = -1) : Attribute
-{
- ///
- /// Defines the minimal item count for the property.
- ///
- public long? MinItems => minItems switch
- {
- -1 => null,
- _ => minItems,
- };
-
- ///
- /// Defines the maximal item count for the property.
- ///
- public long? MaxItems => maxItems switch
- {
- -1 => null,
- _ => maxItems,
- };
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Define minimum and maximum items count for an array property.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class ItemsAttribute(long minItems = -1, long maxItems = -1) : Attribute
+{
+ ///
+ /// Defines the minimal item count for the property.
+ ///
+ public long? MinItems => minItems switch
+ {
+ -1 => null,
+ _ => minItems,
+ };
+
+ ///
+ /// Defines the maximal item count for the property.
+ ///
+ public long? MaxItems => maxItems switch
+ {
+ -1 => null,
+ _ => maxItems,
+ };
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/KubernetesEntityShortNamesAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/KubernetesEntityShortNamesAttribute.cs
index 6ae22093..67b42040 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/KubernetesEntityShortNamesAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/KubernetesEntityShortNamesAttribute.cs
@@ -1,13 +1,13 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Define "shortNames" for CRDs.
-///
-[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-public class KubernetesEntityShortNamesAttribute(params string[] shortNames) : Attribute
-{
- ///
- /// Array of shortnames that should be attached to CRDs.
- ///
- public string[] ShortNames => shortNames;
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Define "shortNames" for CRDs.
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public class KubernetesEntityShortNamesAttribute(params string[] shortNames) : Attribute
+{
+ ///
+ /// Array of shortnames that should be attached to CRDs.
+ ///
+ public string[] ShortNames => shortNames;
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/LengthAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/LengthAttribute.cs
index a960302b..22611cf4 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/LengthAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/LengthAttribute.cs
@@ -1,26 +1,26 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines length limits for properties.
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class LengthAttribute(long minLength = -1, long maxLength = -1) : Attribute
-{
- ///
- /// Define the minimum length.
- ///
- public long? MinLength => minLength switch
- {
- -1 => null,
- _ => minLength,
- };
-
- ///
- /// Define the maximum length.
- ///
- public long? MaxLength => maxLength switch
- {
- -1 => null,
- _ => maxLength,
- };
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines length limits for properties.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class LengthAttribute(long minLength = -1, long maxLength = -1) : Attribute
+{
+ ///
+ /// Define the minimum length.
+ ///
+ public long? MinLength => minLength switch
+ {
+ -1 => null,
+ _ => minLength,
+ };
+
+ ///
+ /// Define the maximum length.
+ ///
+ public long? MaxLength => maxLength switch
+ {
+ -1 => null,
+ _ => maxLength,
+ };
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/MultipleOfAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/MultipleOfAttribute.cs
index 66fe6f64..6bd8c32e 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/MultipleOfAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/MultipleOfAttribute.cs
@@ -1,13 +1,13 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines the factor that a numeric value must adhere to.
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class MultipleOfAttribute(double value) : Attribute
-{
- ///
- /// The property should be a multiple of this value.
- ///
- public double Value => value;
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines the factor that a numeric value must adhere to.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class MultipleOfAttribute(double value) : Attribute
+{
+ ///
+ /// The property should be a multiple of this value.
+ ///
+ public double Value => value;
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/PatternAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/PatternAttribute.cs
index ad4fd054..ee0ea269 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/PatternAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/PatternAttribute.cs
@@ -1,13 +1,13 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Define a regex validator for the property.
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class PatternAttribute(string regexPattern) : Attribute
-{
- ///
- /// The regex pattern to be used.
- ///
- public string RegexPattern => regexPattern;
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Define a regex validator for the property.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class PatternAttribute(string regexPattern) : Attribute
+{
+ ///
+ /// The regex pattern to be used.
+ ///
+ public string RegexPattern => regexPattern;
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/PreserveUnknownFieldsAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/PreserveUnknownFieldsAttribute.cs
index 8d092cea..d2181d87 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/PreserveUnknownFieldsAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/PreserveUnknownFieldsAttribute.cs
@@ -1,8 +1,8 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines that a property should keep unknown fields
-/// so that kubernetes does not purge additional structures.
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class PreserveUnknownFieldsAttribute : Attribute;
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines that a property should keep unknown fields
+/// so that kubernetes does not purge additional structures.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class PreserveUnknownFieldsAttribute : Attribute;
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/RangeMaximum.cs b/src/KubeOps.Abstractions/Entities/Attributes/RangeMaximum.cs
index aed36e26..ba388b00 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/RangeMaximum.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/RangeMaximum.cs
@@ -1,18 +1,18 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines a range maximum for a numeric property.
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class RangeMaximumAttribute(double maximum, bool exclusiveMaximum = false) : Attribute
-{
- ///
- /// Maximum value to be set.
- ///
- public double Maximum => maximum;
-
- ///
- /// Defines if the maximum value is included or excluded.
- ///
- public bool ExclusiveMaximum => exclusiveMaximum;
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines a range maximum for a numeric property.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class RangeMaximumAttribute(double maximum, bool exclusiveMaximum = false) : Attribute
+{
+ ///
+ /// Maximum value to be set.
+ ///
+ public double Maximum => maximum;
+
+ ///
+ /// Defines if the maximum value is included or excluded.
+ ///
+ public bool ExclusiveMaximum => exclusiveMaximum;
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/RangeMinimum.cs b/src/KubeOps.Abstractions/Entities/Attributes/RangeMinimum.cs
index 78bc5fb6..21168260 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/RangeMinimum.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/RangeMinimum.cs
@@ -1,18 +1,18 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines a range minimum for a numeric property.
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class RangeMinimumAttribute(double minimum, bool exclusiveMinimum = false) : Attribute
-{
- ///
- /// Minimum value to be set.
- ///
- public double Minimum => minimum;
-
- ///
- /// Defines if the minimum value is included or excluded.
- ///
- public bool ExclusiveMinimum => exclusiveMinimum;
-}
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines a range minimum for a numeric property.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class RangeMinimumAttribute(double minimum, bool exclusiveMinimum = false) : Attribute
+{
+ ///
+ /// Minimum value to be set.
+ ///
+ public double Minimum => minimum;
+
+ ///
+ /// Defines if the minimum value is included or excluded.
+ ///
+ public bool ExclusiveMinimum => exclusiveMinimum;
+}
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/RequiredAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/RequiredAttribute.cs
index 2ca6d6c5..9574fa74 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/RequiredAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/RequiredAttribute.cs
@@ -1,7 +1,7 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// Defines a property of a specification as required.
-///
-[AttributeUsage(AttributeTargets.Property)]
-public class RequiredAttribute : Attribute;
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// Defines a property of a specification as required.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class RequiredAttribute : Attribute;
diff --git a/src/KubeOps.Abstractions/Entities/Attributes/StorageVersionAttribute.cs b/src/KubeOps.Abstractions/Entities/Attributes/StorageVersionAttribute.cs
index db48b663..cff4ab19 100644
--- a/src/KubeOps.Abstractions/Entities/Attributes/StorageVersionAttribute.cs
+++ b/src/KubeOps.Abstractions/Entities/Attributes/StorageVersionAttribute.cs
@@ -1,11 +1,11 @@
-namespace KubeOps.Abstractions.Entities.Attributes;
-
-///
-/// This attribute marks an entity as the storage version of
-/// an entity. Only one storage version must be set.
-/// If none of the versions define this attribute, the "newest"
-/// one is taken according to the kubernetes versioning rules.
-/// GA > Beta > Alpha > non versions.
-///
-[AttributeUsage(AttributeTargets.Class)]
-public class StorageVersionAttribute : Attribute;
+namespace KubeOps.Abstractions.Entities.Attributes;
+
+///
+/// This attribute marks an entity as the storage version of
+/// an entity. Only one storage version must be set.
+/// If none of the versions define this attribute, the "newest"
+/// one is taken according to the kubernetes versioning rules.
+/// GA > Beta > Alpha > non versions.
+///
+[AttributeUsage(AttributeTargets.Class)]
+public class StorageVersionAttribute : Attribute;
diff --git a/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity.cs b/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity.cs
index 7c453c75..90feafef 100644
--- a/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity.cs
+++ b/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity.cs
@@ -1,16 +1,16 @@
-using k8s;
-using k8s.Models;
-
-namespace KubeOps.Abstractions.Entities;
-
-///
-/// Base class for custom Kubernetes entities. The interface
-/// can be used on its own, but this class provides convenience initializers.
-///
-public abstract class CustomKubernetesEntity : KubernetesObject, IKubernetesObject
-{
- ///
- /// The metadata of the kubernetes object.
- ///
- public V1ObjectMeta Metadata { get; set; } = new();
-}
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Entities;
+
+///
+/// Base class for custom Kubernetes entities. The interface
+/// can be used on its own, but this class provides convenience initializers.
+///
+public abstract class CustomKubernetesEntity : KubernetesObject, IKubernetesObject
+{
+ ///
+ /// The metadata of the kubernetes object.
+ ///
+ public V1ObjectMeta Metadata { get; set; } = new();
+}
diff --git a/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity{TSpec,TStatus}.cs b/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity{TSpec,TStatus}.cs
index b6837686..36ee3d97 100644
--- a/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity{TSpec,TStatus}.cs
+++ b/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity{TSpec,TStatus}.cs
@@ -1,22 +1,22 @@
-using k8s;
-
-namespace KubeOps.Abstractions.Entities;
-
-///
-/// Defines a custom Kubernetes entity.
-/// This entity contains a spec (like )
-/// and a status () which can be updated to reflect the state
-/// of the entity.
-///
-/// The type of the specified data.
-/// The type of the status data.
-public abstract class CustomKubernetesEntity : CustomKubernetesEntity, IStatus
- where TSpec : new()
- where TStatus : new()
-{
- ///
- /// Status object for the entity.
- ///
- // [JsonPropertyName("status")]
- public TStatus Status { get; set; } = new();
-}
+using k8s;
+
+namespace KubeOps.Abstractions.Entities;
+
+///
+/// Defines a custom Kubernetes entity.
+/// This entity contains a spec (like )
+/// and a status () which can be updated to reflect the state
+/// of the entity.
+///
+/// The type of the specified data.
+/// The type of the status data.
+public abstract class CustomKubernetesEntity : CustomKubernetesEntity, IStatus
+ where TSpec : new()
+ where TStatus : new()
+{
+ ///
+ /// Status object for the entity.
+ ///
+ // [JsonPropertyName("status")]
+ public TStatus Status { get; set; } = new();
+}
diff --git a/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity{TSpec}.cs b/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity{TSpec}.cs
index 5a0353d4..d9d13e83 100644
--- a/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity{TSpec}.cs
+++ b/src/KubeOps.Abstractions/Entities/CustomKubernetesEntity{TSpec}.cs
@@ -1,17 +1,17 @@
-using k8s;
-
-namespace KubeOps.Abstractions.Entities;
-
-///
-/// Defines a custom kubernetes entity which can be used in finalizers and controllers.
-/// This entity contains a , which means in contains specified data.
-///
-/// The type of the specified data.
-public abstract class CustomKubernetesEntity : CustomKubernetesEntity, ISpec
- where TSpec : new()
-{
- ///
- /// Specification of the kubernetes object.
- ///
- public TSpec Spec { get; set; } = new();
-}
+using k8s;
+
+namespace KubeOps.Abstractions.Entities;
+
+///
+/// Defines a custom kubernetes entity which can be used in finalizers and controllers.
+/// This entity contains a , which means in contains specified data.
+///
+/// The type of the specified data.
+public abstract class CustomKubernetesEntity : CustomKubernetesEntity, ISpec
+ where TSpec : new()
+{
+ ///
+ /// Specification of the kubernetes object.
+ ///
+ public TSpec Spec { get; set; } = new();
+}
diff --git a/src/KubeOps.Abstractions/Entities/EntityList.cs b/src/KubeOps.Abstractions/Entities/EntityList.cs
index 769f917e..5071e7eb 100644
--- a/src/KubeOps.Abstractions/Entities/EntityList.cs
+++ b/src/KubeOps.Abstractions/Entities/EntityList.cs
@@ -1,22 +1,22 @@
-using k8s;
-using k8s.Models;
-
-namespace KubeOps.Abstractions.Entities;
-
-///
-/// Type for a list of entities.
-///
-/// Type for the list entries.
-public class EntityList : KubernetesObject
- where T : IKubernetesObject
-{
- ///
- /// Official list metadata object of kubernetes.
- ///
- public V1ListMeta Metadata { get; set; } = new();
-
- ///
- /// The list of items.
- ///
- public IList Items { get; set; } = new List();
-}
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Entities;
+
+///
+/// Type for a list of entities.
+///
+/// Type for the list entries.
+public class EntityList : KubernetesObject
+ where T : IKubernetesObject
+{
+ ///
+ /// Official list metadata object of kubernetes.
+ ///
+ public V1ListMeta Metadata { get; set; } = new();
+
+ ///
+ /// The list of items.
+ ///
+ public IList Items { get; set; } = new List();
+}
diff --git a/src/KubeOps.Abstractions/Entities/EntityMetadata.cs b/src/KubeOps.Abstractions/Entities/EntityMetadata.cs
index c72c2ecc..3b035caa 100644
--- a/src/KubeOps.Abstractions/Entities/EntityMetadata.cs
+++ b/src/KubeOps.Abstractions/Entities/EntityMetadata.cs
@@ -1,28 +1,28 @@
-namespace KubeOps.Abstractions.Entities;
-
-///
-/// Metadata for a given entity.
-///
-/// The kind of the entity (e.g. deployment).
-/// Version (e.g. v1 or v2-alpha).
-/// The group in Kubernetes (e.g. "testing.dev").
-/// An optional plural name. Defaults to the singular name with an added "s".
-public record EntityMetadata(string Kind, string Version, string? Group = null, string? Plural = null)
-{
- ///
- /// Kind of the entity when used in a list.
- ///
- public string ListKind => $"{Kind}List";
-
- ///
- /// Name of the singular entity.
- ///
- public string SingularName => Kind.ToLowerInvariant();
-
- ///
- /// Name of the plural entity.
- ///
- public string PluralName => (Plural ?? $"{Kind}s").ToLowerInvariant();
-
- public string GroupWithVersion => $"{Group ?? string.Empty}/{Version}".TrimStart('/');
-}
+namespace KubeOps.Abstractions.Entities;
+
+///
+/// Metadata for a given entity.
+///
+/// The kind of the entity (e.g. deployment).
+/// Version (e.g. v1 or v2-alpha).
+/// The group in Kubernetes (e.g. "testing.dev").
+/// An optional plural name. Defaults to the singular name with an added "s".
+public record EntityMetadata(string Kind, string Version, string? Group = null, string? Plural = null)
+{
+ ///
+ /// Kind of the entity when used in a list.
+ ///
+ public string ListKind => $"{Kind}List";
+
+ ///
+ /// Name of the singular entity.
+ ///
+ public string SingularName => Kind.ToLowerInvariant();
+
+ ///
+ /// Name of the plural entity.
+ ///
+ public string PluralName => (Plural ?? $"{Kind}s").ToLowerInvariant();
+
+ public string GroupWithVersion => $"{Group ?? string.Empty}/{Version}".TrimStart('/');
+}
diff --git a/src/KubeOps.Abstractions/Entities/EntityScope.cs b/src/KubeOps.Abstractions/Entities/EntityScope.cs
index b7be754d..ad627ca7 100644
--- a/src/KubeOps.Abstractions/Entities/EntityScope.cs
+++ b/src/KubeOps.Abstractions/Entities/EntityScope.cs
@@ -1,18 +1,18 @@
-namespace KubeOps.Abstractions.Entities;
-
-///
-/// Scope of the resource. Custom entities (resources) in Kubernetes
-/// can either be namespaced or cluster-wide.
-///
-public enum EntityScope
-{
- ///
- /// The resource is namespace.
- ///
- Namespaced,
-
- ///
- /// The resource is cluster-wide.
- ///
- Cluster,
-}
+namespace KubeOps.Abstractions.Entities;
+
+///
+/// Scope of the resource. Custom entities (resources) in Kubernetes
+/// can either be namespaced or cluster-wide.
+///
+public enum EntityScope
+{
+ ///
+ /// The resource is namespace.
+ ///
+ Namespaced,
+
+ ///
+ /// The resource is cluster-wide.
+ ///
+ Cluster,
+}
diff --git a/src/KubeOps.Abstractions/Entities/Extensions.cs b/src/KubeOps.Abstractions/Entities/Extensions.cs
index 28aa46ac..cd69d1c8 100644
--- a/src/KubeOps.Abstractions/Entities/Extensions.cs
+++ b/src/KubeOps.Abstractions/Entities/Extensions.cs
@@ -1,89 +1,89 @@
-using k8s;
-using k8s.Models;
-
-namespace KubeOps.Abstractions.Entities;
-
-///
-/// Method extensions for .
-///
-public static class Extensions
-{
- ///
- /// Sets the resource version of the specified Kubernetes object to the specified value.
- ///
- /// The type of the Kubernetes object.
- /// The Kubernetes object.
- /// The resource version to set.
- /// The Kubernetes object with the updated resource version.
- public static TEntity WithResourceVersion(
- this TEntity entity,
- string resourceVersion)
- where TEntity : IKubernetesObject
- {
- entity.EnsureMetadata().ResourceVersion = resourceVersion;
- return entity;
- }
-
- ///
- /// Sets the resource version of the specified Kubernetes object to the resource version of another object.
- ///
- /// The type of the Kubernetes object.
- /// The Kubernetes object.
- /// The other Kubernetes object.
- /// The Kubernetes object with the updated resource version.
- public static TEntity WithResourceVersion(
- this TEntity entity,
- TEntity other)
- where TEntity : IKubernetesObject
- {
- entity.EnsureMetadata().ResourceVersion = other.ResourceVersion();
- return entity;
- }
-
- ///
- /// Create a of a kubernetes object.
- ///
- /// The object that should be translated.
- /// The created .
- public static V1ObjectReference MakeObjectReference(this IKubernetesObject kubernetesObject)
- => new()
- {
- ApiVersion = kubernetesObject.ApiVersion,
- Kind = kubernetesObject.Kind,
- Name = kubernetesObject.Metadata.Name,
- NamespaceProperty = kubernetesObject.Metadata.NamespaceProperty,
- ResourceVersion = kubernetesObject.Metadata.ResourceVersion,
- Uid = kubernetesObject.Metadata.Uid,
- };
-
- ///
- /// Ensures the object contains owner references and adds the owner to the list.
- ///
- /// The resource that is owned by another resource.
- /// The owner to add.
- /// The type of the entity.
- /// The resource with the added owner reference.
- public static TEntity WithOwnerReference(
- this TEntity resource,
- IKubernetesObject owner)
- where TEntity : IKubernetesObject
- {
- resource.EnsureMetadata().EnsureOwnerReferences().Add(owner.MakeOwnerReference());
- return resource;
- }
-
- ///
- /// Create a out of a kubernetes object.
- ///
- /// The object that should be translated.
- /// The created .
- public static V1OwnerReference MakeOwnerReference(this IKubernetesObject kubernetesObject)
- => new(
- kubernetesObject.ApiVersion,
- kubernetesObject.Kind,
- kubernetesObject.Metadata.Name,
- kubernetesObject.Metadata.Uid);
-
- private static IList EnsureOwnerReferences(this V1ObjectMeta meta) =>
- meta.OwnerReferences ??= new List();
-}
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Entities;
+
+///
+/// Method extensions for .
+///
+public static class Extensions
+{
+ ///
+ /// Sets the resource version of the specified Kubernetes object to the specified value.
+ ///
+ /// The type of the Kubernetes object.
+ /// The Kubernetes object.
+ /// The resource version to set.
+ /// The Kubernetes object with the updated resource version.
+ public static TEntity WithResourceVersion(
+ this TEntity entity,
+ string resourceVersion)
+ where TEntity : IKubernetesObject
+ {
+ entity.EnsureMetadata().ResourceVersion = resourceVersion;
+ return entity;
+ }
+
+ ///
+ /// Sets the resource version of the specified Kubernetes object to the resource version of another object.
+ ///
+ /// The type of the Kubernetes object.
+ /// The Kubernetes object.
+ /// The other Kubernetes object.
+ /// The Kubernetes object with the updated resource version.
+ public static TEntity WithResourceVersion(
+ this TEntity entity,
+ TEntity other)
+ where TEntity : IKubernetesObject
+ {
+ entity.EnsureMetadata().ResourceVersion = other.ResourceVersion();
+ return entity;
+ }
+
+ ///
+ /// Create a of a kubernetes object.
+ ///
+ /// The object that should be translated.
+ /// The created .
+ public static V1ObjectReference MakeObjectReference(this IKubernetesObject kubernetesObject)
+ => new()
+ {
+ ApiVersion = kubernetesObject.ApiVersion,
+ Kind = kubernetesObject.Kind,
+ Name = kubernetesObject.Metadata.Name,
+ NamespaceProperty = kubernetesObject.Metadata.NamespaceProperty,
+ ResourceVersion = kubernetesObject.Metadata.ResourceVersion,
+ Uid = kubernetesObject.Metadata.Uid,
+ };
+
+ ///
+ /// Ensures the object contains owner references and adds the owner to the list.
+ ///
+ /// The resource that is owned by another resource.
+ /// The owner to add.
+ /// The type of the entity.
+ /// The resource with the added owner reference.
+ public static TEntity WithOwnerReference(
+ this TEntity resource,
+ IKubernetesObject owner)
+ where TEntity : IKubernetesObject
+ {
+ resource.EnsureMetadata().EnsureOwnerReferences().Add(owner.MakeOwnerReference());
+ return resource;
+ }
+
+ ///
+ /// Create a out of a kubernetes object.
+ ///
+ /// The object that should be translated.
+ /// The created .
+ public static V1OwnerReference MakeOwnerReference(this IKubernetesObject kubernetesObject)
+ => new(
+ kubernetesObject.ApiVersion,
+ kubernetesObject.Kind,
+ kubernetesObject.Metadata.Name,
+ kubernetesObject.Metadata.Uid);
+
+ private static IList EnsureOwnerReferences(this V1ObjectMeta meta) =>
+ meta.OwnerReferences ??= new List();
+}
diff --git a/src/KubeOps.Abstractions/Entities/PrinterColumnPriority.cs b/src/KubeOps.Abstractions/Entities/PrinterColumnPriority.cs
index ec1af51d..fb205292 100644
--- a/src/KubeOps.Abstractions/Entities/PrinterColumnPriority.cs
+++ b/src/KubeOps.Abstractions/Entities/PrinterColumnPriority.cs
@@ -1,17 +1,17 @@
-namespace KubeOps.Abstractions.Entities;
-
-///
-/// Specifies the priority of a column in an additional printer view.
-///
-public enum PrinterColumnPriority
-{
- ///
- /// The column is displayed in the standard view.
- ///
- StandardView,
-
- ///
- /// The column is displayed in the wide view.
- ///
- WideView,
-}
+namespace KubeOps.Abstractions.Entities;
+
+///
+/// Specifies the priority of a column in an additional printer view.
+///
+public enum PrinterColumnPriority
+{
+ ///
+ /// The column is displayed in the standard view.
+ ///
+ StandardView,
+
+ ///
+ /// The column is displayed in the wide view.
+ ///
+ WideView,
+}
diff --git a/src/KubeOps.Abstractions/Events/EventType.cs b/src/KubeOps.Abstractions/Events/EventType.cs
index 68183ca0..4d4ce659 100644
--- a/src/KubeOps.Abstractions/Events/EventType.cs
+++ b/src/KubeOps.Abstractions/Events/EventType.cs
@@ -1,20 +1,20 @@
-using k8s.Models;
-
-namespace KubeOps.Abstractions.Events;
-
-///
-/// The type of a .
-/// The event type will be stringified and used as .
-///
-public enum EventType
-{
- ///
- /// A normal event, informative value.
- ///
- Normal,
-
- ///
- /// A warning, something might went wrong.
- ///
- Warning,
-}
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Events;
+
+///
+/// The type of a .
+/// The event type will be stringified and used as .
+///
+public enum EventType
+{
+ ///
+ /// A normal event, informative value.
+ ///
+ Normal,
+
+ ///
+ /// A warning, something might went wrong.
+ ///
+ Warning,
+}
diff --git a/src/KubeOps.Abstractions/Events/IEventPublisherFactory.cs b/src/KubeOps.Abstractions/Events/IEventPublisherFactory.cs
new file mode 100644
index 00000000..6ad7d930
--- /dev/null
+++ b/src/KubeOps.Abstractions/Events/IEventPublisherFactory.cs
@@ -0,0 +1,13 @@
+namespace KubeOps.Abstractions.Events;
+
+///
+/// Represents a type used to create s for clients and controllers.
+///
+public interface IEventPublisherFactory
+{
+ ///
+ /// Creates a new event publisher.
+ ///
+ /// The .
+ EventPublisher Create();
+}
diff --git a/src/KubeOps.Abstractions/Events/Publisher.cs b/src/KubeOps.Abstractions/Events/Publisher.cs
index cef72725..769133c4 100644
--- a/src/KubeOps.Abstractions/Events/Publisher.cs
+++ b/src/KubeOps.Abstractions/Events/Publisher.cs
@@ -1,46 +1,51 @@
-using k8s;
-using k8s.Models;
-
-namespace KubeOps.Abstractions.Events;
-
-///
-/// This injectable delegate publishes events on entities. Events are created in the same
-/// namespace as the provided entity. However, if no namespace is provided (for example in
-/// cluster wide entities), the "default" namespace is used.
-///
-/// The delegate creates a if none does exist or updates the
-/// count and last seen timestamp if the same event already fired.
-///
-/// Events have a hex encoded name of a SHA512 hash. For the delegate to update
-/// an event, the entity, reason, message, and type must be the same.
-///
-/// The entity that is involved with the event.
-/// The reason string. This should be a machine readable reason string.
-/// A human readable string for the event.
-/// The of the event (either normal or warning).
-/// A task that finishes when the event is created or updated.
-///
-/// Controller that fires a simple reconcile event on any entity it encounters.
-/// Note that the publication of an event does not trigger another reconcile.
-///
-/// public class V1TestEntityController : IEntityController<V1TestEntity>
-/// {
-/// private readonly EventPublisher _eventPublisher;
-///
-/// public V1TestEntityController()
-/// {
-/// _eventPublisher = eventPublisher;
-/// }
-///
-/// public async Task ReconcileAsync(V1TestEntity entity)
-/// {
-/// await _eventPublisher(entity, "Reconciled", "Entity was reconciled.");
-/// }
-/// }
-///
-///
-public delegate Task EventPublisher(
- IKubernetesObject entity,
- string reason,
- string message,
- EventType type = EventType.Normal);
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Events;
+
+///
+///
+/// Publish a new event for . Events are created in the same namespace. However,
+/// if no namespace is provided (e.g. for cluster-wide entities), the "default" namespace is used.
+///
+///
+/// This effectively creates a new or updates the and
+/// of the existing .
+///
+///
+///
+/// Events have a hex encoded name of a SHA512 hash. For the delegate to update an event,
+/// , , and must be the same.
+///
+/// The entity that is involved with the event.
+/// The reason string. This should be a machine readable reason string.
+/// A human readable string for the event.
+/// The of the event.
+/// The token to monitor for cancellation requests.
+/// A task that finishes when the event is created or updated.
+///
+/// Controller that fires a simple reconcile event on any entity it encounters.
+/// Note that the publication of an event does not trigger another reconcile.
+///
+/// public class V1TestEntityController : IEntityController<V1TestEntity>
+/// {
+/// private readonly EventPublisher _eventPublisher;
+///
+/// public V1TestEntityController()
+/// {
+/// _eventPublisher = eventPublisher;
+/// }
+///
+/// public async Task ReconcileAsync(V1TestEntity entity, CancellationToken token)
+/// {
+/// await _eventPublisher(entity, "Reconciled", "Entity was reconciled.", cancellationToken: token);
+/// }
+/// }
+///
+///
+public delegate Task EventPublisher(
+ IKubernetesObject entity,
+ string reason,
+ string message,
+ EventType type = EventType.Normal,
+ CancellationToken cancellationToken = default);
diff --git a/src/KubeOps.Abstractions/Finalizer/EntityFinalizerAttacher.cs b/src/KubeOps.Abstractions/Finalizer/EntityFinalizerAttacher.cs
index 742a0d17..fb910242 100644
--- a/src/KubeOps.Abstractions/Finalizer/EntityFinalizerAttacher.cs
+++ b/src/KubeOps.Abstractions/Finalizer/EntityFinalizerAttacher.cs
@@ -1,47 +1,50 @@
-using k8s;
-using k8s.Models;
-
-namespace KubeOps.Abstractions.Finalizer;
-
-///
-///
-/// Injectable delegate for finalizers. This delegate is used to attach a finalizer
-/// with its identifier to an entity. When injected, simply call the delegate with
-/// the entity to attach the finalizer.
-///
-///
-/// As with other (possibly) mutating calls, use the returned entity for further
-/// modification and Kubernetes client interactions, since the resource version
-/// is updated each time the entity is modified.
-///
-///
-/// Note that the operator needs RBAC access to modify the list of
-/// finalizers on the entity.
-///
-///
-/// The type of the entity finalizer.
-/// The type of the Kubernetes entity.
-/// The instance of the entity, that the finalizer is attached if needed.
-/// A that resolves when the finalizer was attached.
-///
-/// Use the finalizer delegate to attach the "FinalizerOne" to the entity as soon
-/// as the entity gets reconciled.
-///
-/// [EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
-/// public class V1TestEntityController : IEntityController<V1TestEntity>
-/// {
-/// private readonly EntityFinalizerAttacher<FinalizerOne, V1TestEntity> _finalizer1;
-///
-/// public V1TestEntityController(
-/// EntityFinalizerAttacher<FinalizerOne, V1TestEntity> finalizer1) => _finalizer1 = finalizer1;
-///
-/// public async Task ReconcileAsync(V1TestEntity entity)
-/// {
-/// entity = await _finalizer1(entity);
-/// }
-/// }
-///
-///
-public delegate Task EntityFinalizerAttacher(TEntity entity)
- where TImplementation : IEntityFinalizer
- where TEntity : IKubernetesObject;
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Finalizer;
+
+///
+///
+/// Injectable delegate for finalizers. This delegate is used to attach a finalizer
+/// with its identifier to an entity. When injected, simply call the delegate with
+/// the entity to attach the finalizer.
+///
+///
+/// As with other (possibly) mutating calls, use the returned entity for further
+/// modification and Kubernetes client interactions, since the resource version
+/// is updated each time the entity is modified.
+///
+///
+/// Note that the operator needs RBAC access to modify the list of
+/// finalizers on the entity.
+///
+///
+/// The type of the entity finalizer.
+/// The type of the Kubernetes entity.
+/// The instance of the entity, that the finalizer is attached if needed.
+/// The token to monitor for cancellation requests.
+/// A that resolves when the finalizer was attached.
+///
+/// Use the finalizer delegate to attach the "FinalizerOne" to the entity as soon
+/// as the entity gets reconciled.
+///
+/// [EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
+/// public class V1TestEntityController : IEntityController<V1TestEntity>
+/// {
+/// private readonly EntityFinalizerAttacher<FinalizerOne, V1TestEntity> _finalizer1;
+///
+/// public V1TestEntityController(
+/// EntityFinalizerAttacher<FinalizerOne, V1TestEntity> finalizer1) => _finalizer1 = finalizer1;
+///
+/// public async Task ReconcileAsync(V1TestEntity entity, CancellationToken token)
+/// {
+/// entity = await _finalizer1(entity, token);
+/// }
+/// }
+///
+///
+public delegate Task EntityFinalizerAttacher(
+ TEntity entity,
+ CancellationToken cancellationToken = default)
+ where TImplementation : IEntityFinalizer
+ where TEntity : IKubernetesObject;
diff --git a/src/KubeOps.Abstractions/Finalizer/IEntityFinalizer{TEntity}.cs b/src/KubeOps.Abstractions/Finalizer/IEntityFinalizer{TEntity}.cs
index 1bb9114c..d0af39fc 100644
--- a/src/KubeOps.Abstractions/Finalizer/IEntityFinalizer{TEntity}.cs
+++ b/src/KubeOps.Abstractions/Finalizer/IEntityFinalizer{TEntity}.cs
@@ -1,20 +1,20 @@
-using k8s;
-using k8s.Models;
-
-namespace KubeOps.Abstractions.Finalizer;
-
-///
-/// Finalizer for an entity.
-///
-/// The type of the entity.
-public interface IEntityFinalizer
- where TEntity : IKubernetesObject
-{
- ///
- /// Finalize an entity that is pending for deletion.
- ///
- /// The kubernetes entity that needs to be finalized.
- /// A task that resolves when the operation is done.
- Task FinalizeAsync(TEntity entity) =>
- Task.CompletedTask;
-}
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Finalizer;
+
+///
+/// Finalizer for an entity.
+///
+/// The type of the entity.
+public interface IEntityFinalizer
+ where TEntity : IKubernetesObject
+{
+ ///
+ /// Finalize an entity that is pending for deletion.
+ ///
+ /// The kubernetes entity that needs to be finalized.
+ /// The token to monitor for cancellation requests.
+ /// A task that resolves when the operation is done.
+ Task FinalizeAsync(TEntity entity, CancellationToken cancellationToken);
+}
diff --git a/src/KubeOps.Abstractions/Finalizer/IEventFinalizerAttacherFactory.cs b/src/KubeOps.Abstractions/Finalizer/IEventFinalizerAttacherFactory.cs
new file mode 100644
index 00000000..0f53adb7
--- /dev/null
+++ b/src/KubeOps.Abstractions/Finalizer/IEventFinalizerAttacherFactory.cs
@@ -0,0 +1,22 @@
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Finalizer;
+
+///
+/// Represents a type used to create for controllers.
+///
+public interface IEventFinalizerAttacherFactory
+{
+ ///
+ /// Creates a new , which attaches the finalizer of
+ /// type to .
+ ///
+ /// The finalizer identifier.
+ /// The finalizer.
+ /// The entity.
+ /// A delegate to attach the finalizer implementation to the entity.
+ EntityFinalizerAttacher Create(string identifier)
+ where TImplementation : class, IEntityFinalizer
+ where TEntity : IKubernetesObject;
+}
diff --git a/src/KubeOps.Abstractions/Kustomize/KustomizationConfig.cs b/src/KubeOps.Abstractions/Kustomize/KustomizationConfig.cs
index a650edab..aee0a57f 100644
--- a/src/KubeOps.Abstractions/Kustomize/KustomizationConfig.cs
+++ b/src/KubeOps.Abstractions/Kustomize/KustomizationConfig.cs
@@ -1,55 +1,55 @@
-using k8s;
-
-namespace KubeOps.Abstractions.Kustomize;
-
-///
-/// (Partial) definition for a kustomization yaml.
-///
-public class KustomizationConfig : KubernetesObject
-{
- public KustomizationConfig()
- {
- ApiVersion = "kustomize.config.k8s.io/v1beta1";
- Kind = "Kustomization";
- }
-
- ///
- /// Namespace that should be set.
- ///
- public string? Namespace { get; set; }
-
- ///
- /// Name prefix that should be set.
- ///
- public string? NamePrefix { get; set; }
-
- ///
- /// Common labels for the resources.
- ///
- public IDictionary? CommonLabels { get; set; }
-
- ///
- /// Resource list.
- ///
- public IList? Resources { get; set; }
-
- ///
- /// List of merge patches.
- ///
- public IList? PatchesStrategicMerge { get; set; }
-
- ///
- /// List of .
- ///
- public IList? Images { get; set; }
-
- ///
- /// List of .
- ///
- public IList? ConfigMapGenerator { get; set; }
-
- ///
- /// List of .
- ///
- public IList? SecretGenerator { get; set; }
-}
+using k8s;
+
+namespace KubeOps.Abstractions.Kustomize;
+
+///
+/// (Partial) definition for a kustomization yaml.
+///
+public class KustomizationConfig : KubernetesObject
+{
+ public KustomizationConfig()
+ {
+ ApiVersion = "kustomize.config.k8s.io/v1beta1";
+ Kind = "Kustomization";
+ }
+
+ ///
+ /// Namespace that should be set.
+ ///
+ public string? Namespace { get; set; }
+
+ ///
+ /// Name prefix that should be set.
+ ///
+ public string? NamePrefix { get; set; }
+
+ ///
+ /// Common labels for the resources.
+ ///
+ public IDictionary? CommonLabels { get; set; }
+
+ ///
+ /// Resource list.
+ ///
+ public IList? Resources { get; set; }
+
+ ///
+ /// List of merge patches.
+ ///
+ public IList? PatchesStrategicMerge { get; set; }
+
+ ///
+ /// List of .
+ ///
+ public IList? Images { get; set; }
+
+ ///
+ /// List of .
+ ///
+ public IList? ConfigMapGenerator { get; set; }
+
+ ///
+ /// List of .
+ ///
+ public IList? SecretGenerator { get; set; }
+}
diff --git a/src/KubeOps.Abstractions/Kustomize/KustomizationConfigMapGenerator.cs b/src/KubeOps.Abstractions/Kustomize/KustomizationConfigMapGenerator.cs
index c2cfa022..16b1bd22 100644
--- a/src/KubeOps.Abstractions/Kustomize/KustomizationConfigMapGenerator.cs
+++ b/src/KubeOps.Abstractions/Kustomize/KustomizationConfigMapGenerator.cs
@@ -1,23 +1,23 @@
-namespace KubeOps.Abstractions.Kustomize;
-
-///
-/// Entity for config map generators in a kustomization.yaml file.
-///
-public class KustomizationConfigMapGenerator
-{
- ///
- /// The name of the config map.
- ///
- public string Name { get; set; } = string.Empty;
-
- ///
- /// List of files that should be added to the generated config map.
- ///
- public IList? Files { get; set; }
-
- ///
- /// Config literals to add to the config map in the form of:
- /// - NAME=value.
- ///
- public IList? Literals { get; set; }
-}
+namespace KubeOps.Abstractions.Kustomize;
+
+///
+/// Entity for config map generators in a kustomization.yaml file.
+///
+public class KustomizationConfigMapGenerator
+{
+ ///
+ /// The name of the config map.
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// List of files that should be added to the generated config map.
+ ///
+ public IList? Files { get; set; }
+
+ ///
+ /// Config literals to add to the config map in the form of:
+ /// - NAME=value.
+ ///
+ public IList? Literals { get; set; }
+}
diff --git a/src/KubeOps.Abstractions/Kustomize/KustomizationImage.cs b/src/KubeOps.Abstractions/Kustomize/KustomizationImage.cs
index ecef2b61..dc0e1009 100644
--- a/src/KubeOps.Abstractions/Kustomize/KustomizationImage.cs
+++ b/src/KubeOps.Abstractions/Kustomize/KustomizationImage.cs
@@ -1,22 +1,22 @@
-namespace KubeOps.Abstractions.Kustomize;
-
-///
-/// Definition for an "image" in a kustomization yaml.
-///
-public class KustomizationImage
-{
- ///
- /// Name of the image.
- ///
- public string Name { get; set; } = string.Empty;
-
- ///
- /// New name of the image.
- ///
- public string NewName { get; set; } = string.Empty;
-
- ///
- /// New tag of the image.
- ///
- public string NewTag { get; set; } = string.Empty;
-}
+namespace KubeOps.Abstractions.Kustomize;
+
+///
+/// Definition for an "image" in a kustomization yaml.
+///
+public class KustomizationImage
+{
+ ///
+ /// Name of the image.
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// New name of the image.
+ ///
+ public string NewName { get; set; } = string.Empty;
+
+ ///
+ /// New tag of the image.
+ ///
+ public string NewTag { get; set; } = string.Empty;
+}
diff --git a/src/KubeOps.Abstractions/Kustomize/KustomizationSecretGenerator.cs b/src/KubeOps.Abstractions/Kustomize/KustomizationSecretGenerator.cs
index 856dce6e..e8cc080e 100644
--- a/src/KubeOps.Abstractions/Kustomize/KustomizationSecretGenerator.cs
+++ b/src/KubeOps.Abstractions/Kustomize/KustomizationSecretGenerator.cs
@@ -1,23 +1,23 @@
-namespace KubeOps.Abstractions.Kustomize;
-
-///
-/// Entitiy for config map generators in a kustomization.yaml file.
-///
-public class KustomizationSecretGenerator
-{
- ///
- /// The name of the config map.
- ///
- public string Name { get; set; } = string.Empty;
-
- ///
- /// List of files that should be added to the generated config map.
- ///
- public IList? Files { get; set; }
-
- ///
- /// Config literals to add to the config map in the form of:
- /// - NAME=value.
- ///
- public IList? Literals { get; set; }
-}
+namespace KubeOps.Abstractions.Kustomize;
+
+///
+/// Entitiy for config map generators in a kustomization.yaml file.
+///
+public class KustomizationSecretGenerator
+{
+ ///
+ /// The name of the config map.
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// List of files that should be added to the generated config map.
+ ///
+ public IList? Files { get; set; }
+
+ ///
+ /// Config literals to add to the config map in the form of:
+ /// - NAME=value.
+ ///
+ public IList? Literals { get; set; }
+}
diff --git a/src/KubeOps.Abstractions/Queue/EntityRequeue.cs b/src/KubeOps.Abstractions/Queue/EntityRequeue.cs
index 036e4ae5..ce951440 100644
--- a/src/KubeOps.Abstractions/Queue/EntityRequeue.cs
+++ b/src/KubeOps.Abstractions/Queue/EntityRequeue.cs
@@ -1,47 +1,47 @@
-using k8s;
-using k8s.Models;
-
-namespace KubeOps.Abstractions.Queue;
-
-///
-/// Injectable delegate for requeueing entities.
-///
-/// Use this delegate when you need to pro-actively reconcile an entity after a
-/// certain amount of time. This is useful if you want to check your entities
-/// periodically.
-///
-///
-/// After the timeout is reached, the entity is fetched
-/// from the API and passed to the controller for reconciliation.
-/// If the entity was deleted in the meantime, the controller will not be called.
-///
-///
-/// If the entity gets modified while the timeout is running, the timer
-/// is canceled and restarted, if another requeue is requested.
-///
-///
-/// The type of the entity.
-/// The instance of the entity that should be requeued.
-/// The time to wait before another reconcile loop is fired.
-///
-/// Use the requeue delegate to repeatedly reconcile an entity after 5 seconds.
-///
-/// [EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
-/// public class V1TestEntityController : IEntityController<V1TestEntity>
-/// {
-/// private readonly EntityRequeue<V1TestEntity> _requeue;
-///
-/// public V1TestEntityController(EntityRequeue<V1TestEntity> requeue)
-/// {
-/// _requeue = requeue;
-/// }
-///
-/// public async Task ReconcileAsync(V1TestEntity entity)
-/// {
-/// _requeue(entity, TimeSpan.FromSeconds(5));
-/// }
-/// }
-///
-///
-public delegate void EntityRequeue(TEntity entity, TimeSpan requeueIn)
- where TEntity : IKubernetesObject;
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Queue;
+
+///
+/// Injectable delegate for requeueing entities.
+///
+/// Use this delegate when you need to pro-actively reconcile an entity after a
+/// certain amount of time. This is useful, if you want to check your entities
+/// periodically.
+///
+///
+/// After the timeout is reached, the entity is fetched
+/// from the API and passed to the controller for reconciliation.
+/// If the entity was deleted in the meantime, the controller will not be called.
+///
+///
+/// If the entity gets modified while the timeout is running, the timer
+/// is canceled and restarted, if another requeue is requested.
+///
+///
+/// The type of the entity.
+/// The instance of the entity that should be requeued.
+/// The time to wait before another reconcile loop is fired.
+///
+/// Use the requeue delegate to repeatedly reconcile an entity after 5 seconds.
+///
+/// [EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
+/// public class V1TestEntityController : IEntityController<V1TestEntity>
+/// {
+/// private readonly EntityRequeue<V1TestEntity> _requeue;
+///
+/// public V1TestEntityController(EntityRequeue<V1TestEntity> requeue)
+/// {
+/// _requeue = requeue;
+/// }
+///
+/// public async Task ReconcileAsync(V1TestEntity entity, CancellationToken token)
+/// {
+/// _requeue(entity, TimeSpan.FromSeconds(5));
+/// }
+/// }
+///
+///
+public delegate void EntityRequeue(TEntity entity, TimeSpan requeueIn)
+ where TEntity : IKubernetesObject;
diff --git a/src/KubeOps.Abstractions/Queue/IEntityRequeueFactory.cs b/src/KubeOps.Abstractions/Queue/IEntityRequeueFactory.cs
new file mode 100644
index 00000000..d32ef8aa
--- /dev/null
+++ b/src/KubeOps.Abstractions/Queue/IEntityRequeueFactory.cs
@@ -0,0 +1,18 @@
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Queue;
+
+///
+/// Represents a type used to create delegates of type for requeueing entities.
+///
+public interface IEntityRequeueFactory
+{
+ ///
+ /// Creates a new for the given type.
+ ///
+ /// The entity type.
+ /// A .
+ EntityRequeue Create()
+ where TEntity : IKubernetesObject;
+}
diff --git a/src/KubeOps.Abstractions/Rbac/EntityRbacAttribute.cs b/src/KubeOps.Abstractions/Rbac/EntityRbacAttribute.cs
index aaf86688..7c244a73 100644
--- a/src/KubeOps.Abstractions/Rbac/EntityRbacAttribute.cs
+++ b/src/KubeOps.Abstractions/Rbac/EntityRbacAttribute.cs
@@ -1,30 +1,30 @@
-namespace KubeOps.Abstractions.Rbac;
-
-///
-/// Generate rbac information for a type.
-/// Attach this attribute to a controller with the type reference to
-/// a custom entity to define rbac needs for this given type(s).
-///
-///
-/// Allow the operator "ALL" access to the V1TestEntity.
-///
-/// [EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
-///
-///
-[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-public class EntityRbacAttribute(params Type[] entities) : RbacAttribute
-{
- ///
- /// List of types that this rbac verbs are valid.
- ///
- public IEnumerable Entities => entities;
-
- ///
- /// Flags ("list") of allowed verbs.
- ///
- /// Yaml example:
- /// "verbs: ["get", "list", "watch"]".
- ///
- ///
- public RbacVerb Verbs { get; init; }
-}
+namespace KubeOps.Abstractions.Rbac;
+
+///
+/// Generate rbac information for a type.
+/// Attach this attribute to a controller with the type reference to
+/// a custom entity to define rbac needs for this given type(s).
+///
+///
+/// Allow the operator "ALL" access to the V1TestEntity.
+///
+/// [EntityRbac(typeof(V1TestEntity), Verbs = RbacVerb.All)]
+///
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public class EntityRbacAttribute(params Type[] entities) : RbacAttribute
+{
+ ///
+ /// List of types that this rbac verbs are valid.
+ ///
+ public IEnumerable Entities => entities;
+
+ ///
+ /// Flags ("list") of allowed verbs.
+ ///
+ /// Yaml example:
+ /// "verbs: ["get", "list", "watch"]".
+ ///
+ ///
+ public RbacVerb Verbs { get; init; }
+}
diff --git a/src/KubeOps.Abstractions/Rbac/GenericRbacAttribute.cs b/src/KubeOps.Abstractions/Rbac/GenericRbacAttribute.cs
index 26e38928..9a7cbad2 100644
--- a/src/KubeOps.Abstractions/Rbac/GenericRbacAttribute.cs
+++ b/src/KubeOps.Abstractions/Rbac/GenericRbacAttribute.cs
@@ -1,45 +1,45 @@
-namespace KubeOps.Abstractions.Rbac;
-
-///
-///
-/// Generic attribute to define rbac needs for the operator.
-/// This needs get generated into rbac - yaml style resources
-/// for installation on a cluster.
-///
-/// The attribute essentially defines the role definition of kubernetes.
-///
-[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-public class GenericRbacAttribute : RbacAttribute
-{
- ///
- /// List of groups.
- ///
- /// Yaml example:
- /// "apiGroups: ...".
- ///
- ///
- public string[] Groups { get; init; } = Array.Empty();
-
- ///
- /// List of resources.
- ///
- /// Yaml example:
- /// "resources: ["pods"]".
- ///
- ///
- public string[] Resources { get; init; } = Array.Empty();
-
- ///
- /// List of urls.
- ///
- public string[] Urls { get; init; } = Array.Empty();
-
- ///
- /// Flags ("list") of allowed verbs.
- ///
- /// Yaml example:
- /// "verbs: ["get", "list", "watch"]".
- ///
- ///
- public RbacVerb Verbs { get; init; }
-}
+namespace KubeOps.Abstractions.Rbac;
+
+///
+///
+/// Generic attribute to define rbac needs for the operator.
+/// This needs get generated into rbac - yaml style resources
+/// for installation on a cluster.
+///
+/// The attribute essentially defines the role definition of kubernetes.
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public class GenericRbacAttribute : RbacAttribute
+{
+ ///
+ /// List of groups.
+ ///
+ /// Yaml example:
+ /// "apiGroups: ...".
+ ///
+ ///
+ public string[] Groups { get; init; } = Array.Empty();
+
+ ///
+ /// List of resources.
+ ///
+ /// Yaml example:
+ /// "resources: ["pods"]".
+ ///
+ ///
+ public string[] Resources { get; init; } = Array.Empty();
+
+ ///
+ /// List of urls.
+ ///
+ public string[] Urls { get; init; } = Array.Empty();
+
+ ///
+ /// Flags ("list") of allowed verbs.
+ ///
+ /// Yaml example:
+ /// "verbs: ["get", "list", "watch"]".
+ ///
+ ///
+ public RbacVerb Verbs { get; init; }
+}
diff --git a/src/KubeOps.Abstractions/Rbac/RbacAttribute.cs b/src/KubeOps.Abstractions/Rbac/RbacAttribute.cs
index 007d7e32..34d16934 100644
--- a/src/KubeOps.Abstractions/Rbac/RbacAttribute.cs
+++ b/src/KubeOps.Abstractions/Rbac/RbacAttribute.cs
@@ -1,7 +1,7 @@
-namespace KubeOps.Abstractions.Rbac;
-
-///
-/// Abstract base class for all RBAC attributes.
-///
-[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
-public abstract class RbacAttribute : Attribute;
+namespace KubeOps.Abstractions.Rbac;
+
+///
+/// Abstract base class for all RBAC attributes.
+///
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public abstract class RbacAttribute : Attribute;
diff --git a/src/KubeOps.Abstractions/Rbac/RbacVerbs.cs b/src/KubeOps.Abstractions/Rbac/RbacVerbs.cs
index 725d70b9..a51671b7 100644
--- a/src/KubeOps.Abstractions/Rbac/RbacVerbs.cs
+++ b/src/KubeOps.Abstractions/Rbac/RbacVerbs.cs
@@ -1,53 +1,53 @@
-namespace KubeOps.Abstractions.Rbac;
-
-///
-/// List of possible rbac verbs.
-///
-[Flags]
-public enum RbacVerb
-{
- ///
- /// No permissions on the resource.
- ///
- None = 0,
-
- ///
- /// All possible permissions.
- ///
- All = 1 << 0,
-
- ///
- /// Retrieve the resource from the api.
- ///
- Get = 1 << 1,
-
- ///
- /// List resources on the api.
- ///
- List = 1 << 2,
-
- ///
- /// Watch for events on resources.
- ///
- Watch = 1 << 3,
-
- ///
- /// Create new instances of the resource.
- ///
- Create = 1 << 4,
-
- ///
- /// Update existing resources.
- ///
- Update = 1 << 5,
-
- ///
- /// Patch resources.
- ///
- Patch = 1 << 6,
-
- ///
- /// Delete resources on the api.
- ///
- Delete = 1 << 7,
-}
+namespace KubeOps.Abstractions.Rbac;
+
+///
+/// List of possible rbac verbs.
+///
+[Flags]
+public enum RbacVerb
+{
+ ///
+ /// No permissions on the resource.
+ ///
+ None = 0,
+
+ ///
+ /// All possible permissions.
+ ///
+ All = 1 << 0,
+
+ ///
+ /// Retrieve the resource from the api.
+ ///
+ Get = 1 << 1,
+
+ ///
+ /// List resources on the api.
+ ///
+ List = 1 << 2,
+
+ ///
+ /// Watch for events on resources.
+ ///
+ Watch = 1 << 3,
+
+ ///
+ /// Create new instances of the resource.
+ ///
+ Create = 1 << 4,
+
+ ///
+ /// Update existing resources.
+ ///
+ Update = 1 << 5,
+
+ ///
+ /// Patch resources.
+ ///
+ Patch = 1 << 6,
+
+ ///
+ /// Delete resources on the api.
+ ///
+ Delete = 1 << 7,
+}
diff --git a/src/KubeOps.Cli/Arguments.cs b/src/KubeOps.Cli/Arguments.cs
index 20991908..1de25a40 100644
--- a/src/KubeOps.Cli/Arguments.cs
+++ b/src/KubeOps.Cli/Arguments.cs
@@ -1,39 +1,39 @@
-using System.CommandLine;
-
-namespace KubeOps.Cli;
-
-internal static class Arguments
-{
- public static readonly Argument SolutionOrProjectFile = new(
- "sln/csproj file",
- () =>
- {
- var projectFile
- = Directory.EnumerateFiles(
- Directory.GetCurrentDirectory(),
- "*.csproj")
- .Select(f => new FileInfo(f))
- .FirstOrDefault();
- var slnFile
- = Directory.EnumerateFiles(
- Directory.GetCurrentDirectory(),
- "*.sln")
- .Select(f => new FileInfo(f))
- .FirstOrDefault();
-
- return (projectFile, slnFile) switch
- {
- ({ } prj, _) => prj,
- (_, { } sln) => sln,
- _ => null,
- };
- },
- "A solution or project file where entities are located. " +
- "If omitted, the current directory is searched for a *.csproj or *.sln file. " +
- "If an *.sln file is used, all projects in the solution (with the newest framework) will be searched for entities. " +
- "This behaviour can be filtered by using the --project and --target-framework option.");
-
- public static readonly Argument OperatorName = new(
- "name",
- "Name of the operator.");
-}
+using System.CommandLine;
+
+namespace KubeOps.Cli;
+
+internal static class Arguments
+{
+ public static readonly Argument SolutionOrProjectFile = new(
+ "sln/csproj file",
+ () =>
+ {
+ var projectFile
+ = Directory.EnumerateFiles(
+ Directory.GetCurrentDirectory(),
+ "*.csproj")
+ .Select(f => new FileInfo(f))
+ .FirstOrDefault();
+ var slnFile
+ = Directory.EnumerateFiles(
+ Directory.GetCurrentDirectory(),
+ "*.sln")
+ .Select(f => new FileInfo(f))
+ .FirstOrDefault();
+
+ return (projectFile, slnFile) switch
+ {
+ ({ } prj, _) => prj,
+ (_, { } sln) => sln,
+ _ => null,
+ };
+ },
+ "A solution or project file where entities are located. " +
+ "If omitted, the current directory is searched for a *.csproj or *.sln file. " +
+ "If an *.sln file is used, all projects in the solution (with the newest framework) will be searched for entities. " +
+ "This behaviour can be filtered by using the --project and --target-framework option.");
+
+ public static readonly Argument OperatorName = new(
+ "name",
+ "Name of the operator.");
+}
diff --git a/src/KubeOps.Cli/Certificates/CertificateGenerator.cs b/src/KubeOps.Cli/Certificates/CertificateGenerator.cs
index cb52f2d3..5d4d04c7 100644
--- a/src/KubeOps.Cli/Certificates/CertificateGenerator.cs
+++ b/src/KubeOps.Cli/Certificates/CertificateGenerator.cs
@@ -1,129 +1,129 @@
-using Org.BouncyCastle.Asn1.X509;
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Generators;
-using Org.BouncyCastle.Crypto.Operators;
-using Org.BouncyCastle.Crypto.Prng;
-using Org.BouncyCastle.Math;
-using Org.BouncyCastle.Security;
-using Org.BouncyCastle.Utilities;
-using Org.BouncyCastle.X509;
-using Org.BouncyCastle.X509.Extension;
-
-namespace KubeOps.Cli.Certificates;
-
-internal static class CertificateGenerator
-{
- public static (X509Certificate Certificate, AsymmetricCipherKeyPair Key) CreateCaCertificate()
- {
- var randomGenerator = new CryptoApiRandomGenerator();
- var random = new SecureRandom(randomGenerator);
-
- // The Certificate Generator
- var certificateGenerator = new X509V3CertificateGenerator();
-
- // Serial Number
- var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
- certificateGenerator.SetSerialNumber(serialNumber);
-
- // Issuer and Subject Name
- var name = new X509Name("CN=Operator Root CA, C=DEV, L=Kubernetes");
- certificateGenerator.SetIssuerDN(name);
- certificateGenerator.SetSubjectDN(name);
-
- // Valid For
- var notBefore = DateTime.UtcNow.Date;
- var notAfter = notBefore.AddYears(5);
- certificateGenerator.SetNotBefore(notBefore);
- certificateGenerator.SetNotAfter(notAfter);
-
- // Cert Extensions
- certificateGenerator.AddExtension(
- X509Extensions.BasicConstraints,
- true,
- new BasicConstraints(true));
- certificateGenerator.AddExtension(
- X509Extensions.KeyUsage,
- true,
- new KeyUsage(KeyUsage.KeyCertSign | KeyUsage.CrlSign | KeyUsage.KeyEncipherment));
-
- // Subject Public Key
- const int keyStrength = 256;
- var keyGenerator = new ECKeyPairGenerator("ECDSA");
- keyGenerator.Init(new KeyGenerationParameters(random, keyStrength));
- var key = keyGenerator.GenerateKeyPair();
-
- certificateGenerator.SetPublicKey(key.Public);
-
- var signatureFactory = new Asn1SignatureFactory("SHA512WITHECDSA", key.Private, random);
- var certificate = certificateGenerator.Generate(signatureFactory);
-
- return (certificate, key);
- }
-
- public static (X509Certificate Certificate, AsymmetricCipherKeyPair Key) CreateServerCertificate(
- (X509Certificate Certificate, AsymmetricCipherKeyPair Key) ca, string serverName, string serverNamespace)
- {
- var randomGenerator = new CryptoApiRandomGenerator();
- var random = new SecureRandom(randomGenerator);
-
- // The Certificate Generator
- var certificateGenerator = new X509V3CertificateGenerator();
-
- // Serial Number
- var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
- certificateGenerator.SetSerialNumber(serialNumber);
-
- // Issuer and Subject Name
- certificateGenerator.SetIssuerDN(ca.Certificate.SubjectDN);
- certificateGenerator.SetSubjectDN(new X509Name("CN=Operator Service, C=DEV, L=Kubernetes"));
-
- // Valid For
- var notBefore = DateTime.UtcNow.Date;
- var notAfter = notBefore.AddYears(5);
- certificateGenerator.SetNotBefore(notBefore);
- certificateGenerator.SetNotAfter(notAfter);
-
- // Cert Extensions
- certificateGenerator.AddExtension(
- X509Extensions.BasicConstraints,
- false,
- new BasicConstraints(false));
- certificateGenerator.AddExtension(
- X509Extensions.KeyUsage,
- true,
- new KeyUsage(KeyUsage.NonRepudiation | KeyUsage.KeyEncipherment | KeyUsage.DigitalSignature));
- certificateGenerator.AddExtension(
- X509Extensions.ExtendedKeyUsage,
- false,
- new ExtendedKeyUsage(KeyPurposeID.id_kp_clientAuth, KeyPurposeID.id_kp_serverAuth));
- certificateGenerator.AddExtension(
- X509Extensions.SubjectKeyIdentifier,
- false,
- new SubjectKeyIdentifierStructure(ca.Key.Public));
- certificateGenerator.AddExtension(
- X509Extensions.AuthorityKeyIdentifier,
- false,
- new AuthorityKeyIdentifierStructure(ca.Certificate));
- certificateGenerator.AddExtension(
- X509Extensions.SubjectAlternativeName,
- false,
- new GeneralNames([
- new GeneralName(GeneralName.DnsName, $"{serverName}.{serverNamespace}.svc"),
- new GeneralName(GeneralName.DnsName, $"*.{serverNamespace}.svc"),
- new GeneralName(GeneralName.DnsName, "*.svc"),
- ]));
-
- // Subject Public Key
- const int keyStrength = 256;
- var keyGenerator = new ECKeyPairGenerator("ECDSA");
- keyGenerator.Init(new KeyGenerationParameters(random, keyStrength));
- var key = keyGenerator.GenerateKeyPair();
-
- certificateGenerator.SetPublicKey(key.Public);
-
- var signatureFactory = new Asn1SignatureFactory("SHA512WITHECDSA", ca.Key.Private, random);
- var certificate = certificateGenerator.Generate(signatureFactory);
-
- return (certificate, key);
- }
-}
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Operators;
+using Org.BouncyCastle.Crypto.Prng;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Extension;
+
+namespace KubeOps.Cli.Certificates;
+
+internal static class CertificateGenerator
+{
+ public static (X509Certificate Certificate, AsymmetricCipherKeyPair Key) CreateCaCertificate()
+ {
+ var randomGenerator = new CryptoApiRandomGenerator();
+ var random = new SecureRandom(randomGenerator);
+
+ // The Certificate Generator
+ var certificateGenerator = new X509V3CertificateGenerator();
+
+ // Serial Number
+ var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
+ certificateGenerator.SetSerialNumber(serialNumber);
+
+ // Issuer and Subject Name
+ var name = new X509Name("CN=Operator Root CA, C=DEV, L=Kubernetes");
+ certificateGenerator.SetIssuerDN(name);
+ certificateGenerator.SetSubjectDN(name);
+
+ // Valid For
+ var notBefore = DateTime.UtcNow.Date;
+ var notAfter = notBefore.AddYears(5);
+ certificateGenerator.SetNotBefore(notBefore);
+ certificateGenerator.SetNotAfter(notAfter);
+
+ // Cert Extensions
+ certificateGenerator.AddExtension(
+ X509Extensions.BasicConstraints,
+ true,
+ new BasicConstraints(true));
+ certificateGenerator.AddExtension(
+ X509Extensions.KeyUsage,
+ true,
+ new KeyUsage(KeyUsage.KeyCertSign | KeyUsage.CrlSign | KeyUsage.KeyEncipherment));
+
+ // Subject Public Key
+ const int keyStrength = 256;
+ var keyGenerator = new ECKeyPairGenerator("ECDSA");
+ keyGenerator.Init(new KeyGenerationParameters(random, keyStrength));
+ var key = keyGenerator.GenerateKeyPair();
+
+ certificateGenerator.SetPublicKey(key.Public);
+
+ var signatureFactory = new Asn1SignatureFactory("SHA512WITHECDSA", key.Private, random);
+ var certificate = certificateGenerator.Generate(signatureFactory);
+
+ return (certificate, key);
+ }
+
+ public static (X509Certificate Certificate, AsymmetricCipherKeyPair Key) CreateServerCertificate(
+ (X509Certificate Certificate, AsymmetricCipherKeyPair Key) ca, string serverName, string serverNamespace)
+ {
+ var randomGenerator = new CryptoApiRandomGenerator();
+ var random = new SecureRandom(randomGenerator);
+
+ // The Certificate Generator
+ var certificateGenerator = new X509V3CertificateGenerator();
+
+ // Serial Number
+ var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
+ certificateGenerator.SetSerialNumber(serialNumber);
+
+ // Issuer and Subject Name
+ certificateGenerator.SetIssuerDN(ca.Certificate.SubjectDN);
+ certificateGenerator.SetSubjectDN(new X509Name("CN=Operator Service, C=DEV, L=Kubernetes"));
+
+ // Valid For
+ var notBefore = DateTime.UtcNow.Date;
+ var notAfter = notBefore.AddYears(5);
+ certificateGenerator.SetNotBefore(notBefore);
+ certificateGenerator.SetNotAfter(notAfter);
+
+ // Cert Extensions
+ certificateGenerator.AddExtension(
+ X509Extensions.BasicConstraints,
+ false,
+ new BasicConstraints(false));
+ certificateGenerator.AddExtension(
+ X509Extensions.KeyUsage,
+ true,
+ new KeyUsage(KeyUsage.NonRepudiation | KeyUsage.KeyEncipherment | KeyUsage.DigitalSignature));
+ certificateGenerator.AddExtension(
+ X509Extensions.ExtendedKeyUsage,
+ false,
+ new ExtendedKeyUsage(KeyPurposeID.id_kp_clientAuth, KeyPurposeID.id_kp_serverAuth));
+ certificateGenerator.AddExtension(
+ X509Extensions.SubjectKeyIdentifier,
+ false,
+ new SubjectKeyIdentifierStructure(ca.Key.Public));
+ certificateGenerator.AddExtension(
+ X509Extensions.AuthorityKeyIdentifier,
+ false,
+ new AuthorityKeyIdentifierStructure(ca.Certificate));
+ certificateGenerator.AddExtension(
+ X509Extensions.SubjectAlternativeName,
+ false,
+ new GeneralNames([
+ new GeneralName(GeneralName.DnsName, $"{serverName}.{serverNamespace}.svc"),
+ new GeneralName(GeneralName.DnsName, $"*.{serverNamespace}.svc"),
+ new GeneralName(GeneralName.DnsName, "*.svc"),
+ ]));
+
+ // Subject Public Key
+ const int keyStrength = 256;
+ var keyGenerator = new ECKeyPairGenerator("ECDSA");
+ keyGenerator.Init(new KeyGenerationParameters(random, keyStrength));
+ var key = keyGenerator.GenerateKeyPair();
+
+ certificateGenerator.SetPublicKey(key.Public);
+
+ var signatureFactory = new Asn1SignatureFactory("SHA512WITHECDSA", ca.Key.Private, random);
+ var certificate = certificateGenerator.Generate(signatureFactory);
+
+ return (certificate, key);
+ }
+}
diff --git a/src/KubeOps.Cli/Certificates/Extensions.cs b/src/KubeOps.Cli/Certificates/Extensions.cs
index a72bf103..dfe53b83 100644
--- a/src/KubeOps.Cli/Certificates/Extensions.cs
+++ b/src/KubeOps.Cli/Certificates/Extensions.cs
@@ -1,22 +1,22 @@
-using System.Text;
-
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.OpenSsl;
-using Org.BouncyCastle.X509;
-
-namespace KubeOps.Cli.Certificates;
-
-internal static class Extensions
-{
- public static string ToPem(this X509Certificate cert) => ObjToPem(cert);
-
- public static string ToPem(this AsymmetricCipherKeyPair key) => ObjToPem(key);
-
- private static string ObjToPem(object obj)
- {
- var sb = new StringBuilder();
- using var writer = new PemWriter(new StringWriter(sb));
- writer.WriteObject(obj);
- return sb.ToString();
- }
-}
+using System.Text;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.OpenSsl;
+using Org.BouncyCastle.X509;
+
+namespace KubeOps.Cli.Certificates;
+
+internal static class Extensions
+{
+ public static string ToPem(this X509Certificate cert) => ObjToPem(cert);
+
+ public static string ToPem(this AsymmetricCipherKeyPair key) => ObjToPem(key);
+
+ private static string ObjToPem(object obj)
+ {
+ var sb = new StringBuilder();
+ using var writer = new PemWriter(new StringWriter(sb));
+ writer.WriteObject(obj);
+ return sb.ToString();
+ }
+}
diff --git a/src/KubeOps.Cli/Commands/Generator/Generate.cs b/src/KubeOps.Cli/Commands/Generator/Generate.cs
index 5287f2a8..ce81a081 100644
--- a/src/KubeOps.Cli/Commands/Generator/Generate.cs
+++ b/src/KubeOps.Cli/Commands/Generator/Generate.cs
@@ -1,23 +1,23 @@
-using System.CommandLine;
-using System.CommandLine.Help;
-
-namespace KubeOps.Cli.Commands.Generator;
-
-internal static class Generate
-{
- public static Command Command
- {
- get
- {
- var cmd = new Command("generate", "Generates elements related to an operator.")
- {
- OperatorGenerator.Command,
- };
- cmd.AddAlias("gen");
- cmd.AddAlias("g");
- cmd.SetHandler(ctx => ctx.HelpBuilder.Write(cmd, Console.Out));
-
- return cmd;
- }
- }
-}
+using System.CommandLine;
+using System.CommandLine.Help;
+
+namespace KubeOps.Cli.Commands.Generator;
+
+internal static class Generate
+{
+ public static Command Command
+ {
+ get
+ {
+ var cmd = new Command("generate", "Generates elements related to an operator.")
+ {
+ OperatorGenerator.Command,
+ };
+ cmd.AddAlias("gen");
+ cmd.AddAlias("g");
+ cmd.SetHandler(ctx => ctx.HelpBuilder.Write(cmd, Console.Out));
+
+ return cmd;
+ }
+ }
+}
diff --git a/src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs b/src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs
index 8313afe5..7e9a86a6 100644
--- a/src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs
+++ b/src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs
@@ -1,178 +1,178 @@
-using System.CommandLine;
-using System.CommandLine.Invocation;
-using System.Text;
-
-using k8s;
-using k8s.Models;
-
-using KubeOps.Abstractions.Kustomize;
-using KubeOps.Cli.Generators;
-using KubeOps.Cli.Output;
-using KubeOps.Cli.Transpilation;
-
-using Spectre.Console;
-
-namespace KubeOps.Cli.Commands.Generator;
-
-internal static class OperatorGenerator
-{
- public static Command Command
- {
- get
- {
- var cmd =
- new Command(
- "operator",
- "Generates all required resources and configs for the operator to be built and run.")
- {
- Options.ClearOutputPath,
- Options.OutputFormat,
- Options.OutputPath,
- Options.SolutionProjectRegex,
- Options.TargetFramework,
- Arguments.OperatorName,
- Arguments.SolutionOrProjectFile,
- };
- cmd.AddAlias("op");
- cmd.SetHandler(Handler);
-
- return cmd;
- }
- }
-
- private static async Task Handler(InvocationContext ctx)
- {
- var name = ctx.ParseResult.GetValueForArgument(Arguments.OperatorName);
- var file = ctx.ParseResult.GetValueForArgument(Arguments.SolutionOrProjectFile);
- var outPath = ctx.ParseResult.GetValueForOption(Options.OutputPath);
- var format = ctx.ParseResult.GetValueForOption(Options.OutputFormat);
-
- var result = new ResultOutput(AnsiConsole.Console, format);
- AnsiConsole.Console.WriteLine("Generate operator resources.");
-
- AnsiConsole.Console.MarkupLine("[green]Load Project/Solution file.[/]");
- var parser = file switch
- {
- { Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(AnsiConsole.Console, file),
- { Extension: ".sln", Exists: true } => await AssemblyLoader.ForSolution(
- AnsiConsole.Console,
- file,
- ctx.ParseResult.GetValueForOption(Options.SolutionProjectRegex),
- ctx.ParseResult.GetValueForOption(Options.TargetFramework)),
- { Exists: false } => throw new FileNotFoundException($"The file {file.Name} does not exist."),
- _ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."),
- };
-
- var mutators = parser.GetMutatedEntities().ToList();
- var validators = parser.GetValidatedEntities().ToList();
- var hasWebhooks = mutators.Count > 0 || validators.Count > 0 || parser.GetConvertedEntities().Any();
-
- AnsiConsole.Console.MarkupLine("[green]Generate RBAC rules.[/]");
- new RbacGenerator(parser, format).Generate(result);
-
- AnsiConsole.Console.MarkupLine("[green]Generate Dockerfile.[/]");
- new DockerfileGenerator(hasWebhooks).Generate(result);
-
- if (hasWebhooks)
- {
- AnsiConsole.Console.MarkupLine(
- "[yellow]The operator contains webhooks of some sort, generating webhook operator specific resources.[/]");
-
- AnsiConsole.Console.MarkupLine("[green]Generate CA and Server certificates.[/]");
- new CertificateGenerator(name, $"{name}-system").Generate(result);
-
- AnsiConsole.Console.MarkupLine("[green]Generate Deployment and Service.[/]");
- new WebhookDeploymentGenerator(format).Generate(result);
-
- var caBundle =
- Encoding.ASCII.GetBytes(
- Convert.ToBase64String(Encoding.ASCII.GetBytes(result["ca.pem"].ToString() ?? string.Empty)));
-
- AnsiConsole.Console.MarkupLine("[green]Generate Validation Webhooks.[/]");
- new ValidationWebhookGenerator(validators, caBundle, format).Generate(result);
-
- AnsiConsole.Console.MarkupLine("[green]Generate Mutation Webhooks.[/]");
- new MutationWebhookGenerator(mutators, caBundle, format).Generate(result);
-
- AnsiConsole.Console.MarkupLine("[green]Generate CRDs.[/]");
- new CrdGenerator(parser, caBundle, format).Generate(result);
- }
- else
- {
- AnsiConsole.Console.MarkupLine("[green]Generate Deployment.[/]");
- new DeploymentGenerator(format).Generate(result);
-
- AnsiConsole.Console.MarkupLine("[green]Generate CRDs.[/]");
- new CrdGenerator(parser, Array.Empty(), format).Generate(result);
- }
-
- result.Add(
- $"namespace.{format.GetFileExtension()}",
- new V1Namespace(metadata: new(name: "system")).Initialize());
-
- result.Add(
- $"kustomization.{format.GetFileExtension()}",
- new KustomizationConfig
- {
- NamePrefix = $"{name}-",
- Namespace = $"{name}-system",
- CommonLabels = new Dictionary { { "operator", name }, },
- Resources = result.DefaultFormatFiles.ToList(),
- Images =
- new List
- {
- new() { Name = "operator", NewName = "accessible-docker-image", NewTag = "latest", },
- },
- ConfigMapGenerator = hasWebhooks
- ? new List
- {
- new()
- {
- Name = "webhook-config",
- Literals = new List
- {
- "KESTREL__ENDPOINTS__HTTP__URL=http://0.0.0.0:5000",
- "KESTREL__ENDPOINTS__HTTPS__URL=https://0.0.0.0:5001",
- "KESTREL__ENDPOINTS__HTTPS__CERTIFICATE__PATH=/certs/svc.pem",
- "KESTREL__ENDPOINTS__HTTPS__CERTIFICATE__KEYPATH=/certs/svc-key.pem",
- },
- },
- }
- : null,
- SecretGenerator = hasWebhooks
- ? new List
- {
- new() { Name = "webhook-ca", Files = new List { "ca.pem", "ca-key.pem", }, },
- new() { Name = "webhook-cert", Files = new List { "svc.pem", "svc-key.pem", }, },
- }
- : null,
- });
-
- if (outPath is not null)
- {
- if (ctx.ParseResult.GetValueForOption(Options.ClearOutputPath))
- {
- AnsiConsole.Console.MarkupLine("[yellow]Clear output path.[/]");
- try
- {
- Directory.Delete(outPath, true);
- }
- catch (DirectoryNotFoundException)
- {
- // the dir is not present, so we don't need to delete it.
- }
- catch (Exception e)
- {
- AnsiConsole.Console.MarkupLine($"[red]Could not clear output path: {e.Message}[/]");
- }
- }
-
- AnsiConsole.Console.MarkupLine($"[green]Write output to {outPath}.[/]");
- await result.Write(outPath);
- }
- else
- {
- result.Write();
- }
- }
-}
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Text;
+
+using k8s;
+using k8s.Models;
+
+using KubeOps.Abstractions.Kustomize;
+using KubeOps.Cli.Generators;
+using KubeOps.Cli.Output;
+using KubeOps.Cli.Transpilation;
+
+using Spectre.Console;
+
+namespace KubeOps.Cli.Commands.Generator;
+
+internal static class OperatorGenerator
+{
+ public static Command Command
+ {
+ get
+ {
+ var cmd =
+ new Command(
+ "operator",
+ "Generates all required resources and configs for the operator to be built and run.")
+ {
+ Options.ClearOutputPath,
+ Options.OutputFormat,
+ Options.OutputPath,
+ Options.SolutionProjectRegex,
+ Options.TargetFramework,
+ Arguments.OperatorName,
+ Arguments.SolutionOrProjectFile,
+ };
+ cmd.AddAlias("op");
+ cmd.SetHandler(Handler);
+
+ return cmd;
+ }
+ }
+
+ private static async Task Handler(InvocationContext ctx)
+ {
+ var name = ctx.ParseResult.GetValueForArgument(Arguments.OperatorName);
+ var file = ctx.ParseResult.GetValueForArgument(Arguments.SolutionOrProjectFile);
+ var outPath = ctx.ParseResult.GetValueForOption(Options.OutputPath);
+ var format = ctx.ParseResult.GetValueForOption(Options.OutputFormat);
+
+ var result = new ResultOutput(AnsiConsole.Console, format);
+ AnsiConsole.Console.WriteLine("Generate operator resources.");
+
+ AnsiConsole.Console.MarkupLine("[green]Load Project/Solution file.[/]");
+ var parser = file switch
+ {
+ { Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(AnsiConsole.Console, file),
+ { Extension: ".sln", Exists: true } => await AssemblyLoader.ForSolution(
+ AnsiConsole.Console,
+ file,
+ ctx.ParseResult.GetValueForOption(Options.SolutionProjectRegex),
+ ctx.ParseResult.GetValueForOption(Options.TargetFramework)),
+ { Exists: false } => throw new FileNotFoundException($"The file {file.Name} does not exist."),
+ _ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."),
+ };
+
+ var mutators = parser.GetMutatedEntities().ToList();
+ var validators = parser.GetValidatedEntities().ToList();
+ var hasWebhooks = mutators.Count > 0 || validators.Count > 0 || parser.GetConvertedEntities().Any();
+
+ AnsiConsole.Console.MarkupLine("[green]Generate RBAC rules.[/]");
+ new RbacGenerator(parser, format).Generate(result);
+
+ AnsiConsole.Console.MarkupLine("[green]Generate Dockerfile.[/]");
+ new DockerfileGenerator(hasWebhooks).Generate(result);
+
+ if (hasWebhooks)
+ {
+ AnsiConsole.Console.MarkupLine(
+ "[yellow]The operator contains webhooks of some sort, generating webhook operator specific resources.[/]");
+
+ AnsiConsole.Console.MarkupLine("[green]Generate CA and Server certificates.[/]");
+ new CertificateGenerator(name, $"{name}-system").Generate(result);
+
+ AnsiConsole.Console.MarkupLine("[green]Generate Deployment and Service.[/]");
+ new WebhookDeploymentGenerator(format).Generate(result);
+
+ var caBundle =
+ Encoding.ASCII.GetBytes(
+ Convert.ToBase64String(Encoding.ASCII.GetBytes(result["ca.pem"].ToString() ?? string.Empty)));
+
+ AnsiConsole.Console.MarkupLine("[green]Generate Validation Webhooks.[/]");
+ new ValidationWebhookGenerator(validators, caBundle, format).Generate(result);
+
+ AnsiConsole.Console.MarkupLine("[green]Generate Mutation Webhooks.[/]");
+ new MutationWebhookGenerator(mutators, caBundle, format).Generate(result);
+
+ AnsiConsole.Console.MarkupLine("[green]Generate CRDs.[/]");
+ new CrdGenerator(parser, caBundle, format).Generate(result);
+ }
+ else
+ {
+ AnsiConsole.Console.MarkupLine("[green]Generate Deployment.[/]");
+ new DeploymentGenerator(format).Generate(result);
+
+ AnsiConsole.Console.MarkupLine("[green]Generate CRDs.[/]");
+ new CrdGenerator(parser, Array.Empty(), format).Generate(result);
+ }
+
+ result.Add(
+ $"namespace.{format.GetFileExtension()}",
+ new V1Namespace(metadata: new(name: "system")).Initialize());
+
+ result.Add(
+ $"kustomization.{format.GetFileExtension()}",
+ new KustomizationConfig
+ {
+ NamePrefix = $"{name}-",
+ Namespace = $"{name}-system",
+ CommonLabels = new Dictionary { { "operator", name }, },
+ Resources = result.DefaultFormatFiles.ToList(),
+ Images =
+ new List
+ {
+ new() { Name = "operator", NewName = "accessible-docker-image", NewTag = "latest", },
+ },
+ ConfigMapGenerator = hasWebhooks
+ ? new List
+ {
+ new()
+ {
+ Name = "webhook-config",
+ Literals = new List
+ {
+ "KESTREL__ENDPOINTS__HTTP__URL=http://0.0.0.0:5000",
+ "KESTREL__ENDPOINTS__HTTPS__URL=https://0.0.0.0:5001",
+ "KESTREL__ENDPOINTS__HTTPS__CERTIFICATE__PATH=/certs/svc.pem",
+ "KESTREL__ENDPOINTS__HTTPS__CERTIFICATE__KEYPATH=/certs/svc-key.pem",
+ },
+ },
+ }
+ : null,
+ SecretGenerator = hasWebhooks
+ ? new List
+ {
+ new() { Name = "webhook-ca", Files = new List { "ca.pem", "ca-key.pem", }, },
+ new() { Name = "webhook-cert", Files = new List { "svc.pem", "svc-key.pem", }, },
+ }
+ : null,
+ });
+
+ if (outPath is not null)
+ {
+ if (ctx.ParseResult.GetValueForOption(Options.ClearOutputPath))
+ {
+ AnsiConsole.Console.MarkupLine("[yellow]Clear output path.[/]");
+ try
+ {
+ Directory.Delete(outPath, true);
+ }
+ catch (DirectoryNotFoundException)
+ {
+ // the dir is not present, so we don't need to delete it.
+ }
+ catch (Exception e)
+ {
+ AnsiConsole.Console.MarkupLine($"[red]Could not clear output path: {e.Message}[/]");
+ }
+ }
+
+ AnsiConsole.Console.MarkupLine($"[green]Write output to {outPath}.[/]");
+ await result.Write(outPath);
+ }
+ else
+ {
+ result.Write();
+ }
+ }
+}
diff --git a/src/KubeOps.Cli/Commands/Management/Install.cs b/src/KubeOps.Cli/Commands/Management/Install.cs
index ed16f0ba..91706bdf 100644
--- a/src/KubeOps.Cli/Commands/Management/Install.cs
+++ b/src/KubeOps.Cli/Commands/Management/Install.cs
@@ -1,112 +1,112 @@
-using System.CommandLine;
-using System.CommandLine.Invocation;
-
-using k8s;
-using k8s.Autorest;
-using k8s.Models;
-
-using KubeOps.Cli.Transpilation;
-using KubeOps.Transpiler;
-
-using Spectre.Console;
-
-namespace KubeOps.Cli.Commands.Management;
-
-internal static class Install
-{
- public static Command Command
- {
- get
- {
- var cmd =
- new Command("install", "Install CRDs into the cluster of the actually selected context.")
- {
- Options.Force,
- Options.SolutionProjectRegex,
- Options.TargetFramework,
- Arguments.SolutionOrProjectFile,
- };
- cmd.AddAlias("i");
- cmd.SetHandler(ctx => Handler(
- AnsiConsole.Console,
- new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig()),
- ctx));
-
- return cmd;
- }
- }
-
- internal static async Task Handler(IAnsiConsole console, IKubernetes client, InvocationContext ctx)
- {
- var file = ctx.ParseResult.GetValueForArgument(Arguments.SolutionOrProjectFile);
- var force = ctx.ParseResult.GetValueForOption(Options.Force);
-
- var parser = file switch
- {
- { Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(console, file),
- { Extension: ".sln", Exists: true } => await AssemblyLoader.ForSolution(
- console,
- file,
- ctx.ParseResult.GetValueForOption(Options.SolutionProjectRegex),
- ctx.ParseResult.GetValueForOption(Options.TargetFramework)),
- { Exists: false } => throw new FileNotFoundException($"The file {file.Name} does not exist."),
- _ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."),
- };
-
- console.WriteLine($"Install CRDs from {file.Name}.");
- var crds = parser.Transpile(parser.GetEntities()).ToList();
- if (crds.Count == 0)
- {
- console.WriteLine("No CRDs found. Exiting.");
- ctx.ExitCode = ExitCodes.Success;
- return;
- }
-
- console.WriteLine($"Found {crds.Count} CRDs.");
- console.WriteLine($"""Starting install into cluster with url "{client.BaseUri}".""");
-
- foreach (var crd in crds)
- {
- console.MarkupLineInterpolated(
- $"""Install [cyan]"{crd.Spec.Group}/{crd.Spec.Names.Kind}"[/] into the cluster.""");
-
- try
- {
- switch (await client.ApiextensionsV1.ListCustomResourceDefinitionAsync(
- fieldSelector: $"metadata.name={crd.Name()}"))
- {
- case { Items: [var existing] }:
- console.MarkupLineInterpolated(
- $"""[yellow]CRD "{crd.Spec.Group}/{crd.Spec.Names.Kind}" already exists.[/]""");
- if (!force && !console.Confirm("[yellow]Should the CRD be overwritten?[/]"))
- {
- ctx.ExitCode = ExitCodes.Aborted;
- return;
- }
-
- crd.Metadata.ResourceVersion = existing.ResourceVersion();
- await client.ApiextensionsV1.ReplaceCustomResourceDefinitionAsync(crd, crd.Name());
- break;
- default:
- await client.ApiextensionsV1.CreateCustomResourceDefinitionAsync(crd);
- break;
- }
-
- console.MarkupLineInterpolated(
- $"""[green]Installed / Updated CRD "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]""");
- }
- catch (HttpOperationException)
- {
- console.WriteLine(
- $"""[red]There was a http (api) error while installing "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]""");
- throw;
- }
- catch (Exception)
- {
- console.WriteLine(
- $"""[red]There was an error while installing "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]""");
- throw;
- }
- }
- }
-}
+using System.CommandLine;
+using System.CommandLine.Invocation;
+
+using k8s;
+using k8s.Autorest;
+using k8s.Models;
+
+using KubeOps.Cli.Transpilation;
+using KubeOps.Transpiler;
+
+using Spectre.Console;
+
+namespace KubeOps.Cli.Commands.Management;
+
+internal static class Install
+{
+ public static Command Command
+ {
+ get
+ {
+ var cmd =
+ new Command("install", "Install CRDs into the cluster of the actually selected context.")
+ {
+ Options.Force,
+ Options.SolutionProjectRegex,
+ Options.TargetFramework,
+ Arguments.SolutionOrProjectFile,
+ };
+ cmd.AddAlias("i");
+ cmd.SetHandler(ctx => Handler(
+ AnsiConsole.Console,
+ new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig()),
+ ctx));
+
+ return cmd;
+ }
+ }
+
+ internal static async Task Handler(IAnsiConsole console, IKubernetes client, InvocationContext ctx)
+ {
+ var file = ctx.ParseResult.GetValueForArgument(Arguments.SolutionOrProjectFile);
+ var force = ctx.ParseResult.GetValueForOption(Options.Force);
+
+ var parser = file switch
+ {
+ { Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(console, file),
+ { Extension: ".sln", Exists: true } => await AssemblyLoader.ForSolution(
+ console,
+ file,
+ ctx.ParseResult.GetValueForOption(Options.SolutionProjectRegex),
+ ctx.ParseResult.GetValueForOption(Options.TargetFramework)),
+ { Exists: false } => throw new FileNotFoundException($"The file {file.Name} does not exist."),
+ _ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."),
+ };
+
+ console.WriteLine($"Install CRDs from {file.Name}.");
+ var crds = parser.Transpile(parser.GetEntities()).ToList();
+ if (crds.Count == 0)
+ {
+ console.WriteLine("No CRDs found. Exiting.");
+ ctx.ExitCode = ExitCodes.Success;
+ return;
+ }
+
+ console.WriteLine($"Found {crds.Count} CRDs.");
+ console.WriteLine($"""Starting install into cluster with url "{client.BaseUri}".""");
+
+ foreach (var crd in crds)
+ {
+ console.MarkupLineInterpolated(
+ $"""Install [cyan]"{crd.Spec.Group}/{crd.Spec.Names.Kind}"[/] into the cluster.""");
+
+ try
+ {
+ switch (await client.ApiextensionsV1.ListCustomResourceDefinitionAsync(
+ fieldSelector: $"metadata.name={crd.Name()}"))
+ {
+ case { Items: [var existing] }:
+ console.MarkupLineInterpolated(
+ $"""[yellow]CRD "{crd.Spec.Group}/{crd.Spec.Names.Kind}" already exists.[/]""");
+ if (!force && !console.Confirm("[yellow]Should the CRD be overwritten?[/]"))
+ {
+ ctx.ExitCode = ExitCodes.Aborted;
+ return;
+ }
+
+ crd.Metadata.ResourceVersion = existing.ResourceVersion();
+ await client.ApiextensionsV1.ReplaceCustomResourceDefinitionAsync(crd, crd.Name());
+ break;
+ default:
+ await client.ApiextensionsV1.CreateCustomResourceDefinitionAsync(crd);
+ break;
+ }
+
+ console.MarkupLineInterpolated(
+ $"""[green]Installed / Updated CRD "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]""");
+ }
+ catch (HttpOperationException)
+ {
+ console.WriteLine(
+ $"""[red]There was a http (api) error while installing "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]""");
+ throw;
+ }
+ catch (Exception)
+ {
+ console.WriteLine(
+ $"""[red]There was an error while installing "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]""");
+ throw;
+ }
+ }
+ }
+}
diff --git a/src/KubeOps.Cli/Commands/Management/Uninstall.cs b/src/KubeOps.Cli/Commands/Management/Uninstall.cs
index e09fbd39..8c249806 100644
--- a/src/KubeOps.Cli/Commands/Management/Uninstall.cs
+++ b/src/KubeOps.Cli/Commands/Management/Uninstall.cs
@@ -1,109 +1,109 @@
-using System.CommandLine;
-using System.CommandLine.Invocation;
-
-using k8s;
-using k8s.Autorest;
-using k8s.Models;
-
-using KubeOps.Cli.Transpilation;
-using KubeOps.Transpiler;
-
-using Spectre.Console;
-
-namespace KubeOps.Cli.Commands.Management;
-
-internal static class Uninstall
-{
- public static Command Command
- {
- get
- {
- var cmd =
- new Command("uninstall", "Uninstall CRDs from the cluster of the actually selected context.")
- {
- Options.Force,
- Options.SolutionProjectRegex,
- Options.TargetFramework,
- Arguments.SolutionOrProjectFile,
- };
- cmd.AddAlias("u");
- cmd.SetHandler(ctx => Handler(
- AnsiConsole.Console,
- new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig()),
- ctx));
-
- return cmd;
- }
- }
-
- internal static async Task Handler(IAnsiConsole console, IKubernetes client, InvocationContext ctx)
- {
- var file = ctx.ParseResult.GetValueForArgument(Arguments.SolutionOrProjectFile);
- var force = ctx.ParseResult.GetValueForOption(Options.Force);
-
- var parser = file switch
- {
- { Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(console, file),
- { Extension: ".sln", Exists: true } => await AssemblyLoader.ForSolution(
- console,
- file,
- ctx.ParseResult.GetValueForOption(Options.SolutionProjectRegex),
- ctx.ParseResult.GetValueForOption(Options.TargetFramework)),
- { Exists: false } => throw new FileNotFoundException($"The file {file.Name} does not exist."),
- _ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."),
- };
-
- console.WriteLine($"Uninstall CRDs from {file.Name}.");
- var crds = parser.Transpile(parser.GetEntities()).ToList();
- if (crds.Count == 0)
- {
- console.WriteLine("No CRDs found. Exiting.");
- ctx.ExitCode = ExitCodes.Success;
- return;
- }
-
- console.WriteLine($"Found {crds.Count} CRDs.");
- if (!force && !console.Confirm("[red]Should the CRDs be uninstalled?[/]", false))
- {
- ctx.ExitCode = ExitCodes.Aborted;
- return;
- }
-
- console.WriteLine($"""Starting uninstall from cluster with url "{client.BaseUri}".""");
-
- foreach (var crd in crds)
- {
- console.MarkupLineInterpolated(
- $"""Uninstall [cyan]"{crd.Spec.Group}/{crd.Spec.Names.Kind}"[/] from the cluster.""");
-
- try
- {
- switch (await client.ApiextensionsV1.ListCustomResourceDefinitionAsync(
- fieldSelector: $"metadata.name={crd.Name()}"))
- {
- case { Items: [var existing] }:
- await client.ApiextensionsV1.DeleteCustomResourceDefinitionAsync(existing.Name());
- console.MarkupLineInterpolated(
- $"""[green]CRD "{crd.Spec.Group}/{crd.Spec.Names.Kind}" deleted.[/]""");
- break;
- default:
- console.MarkupLineInterpolated(
- $"""[green]CRD "{crd.Spec.Group}/{crd.Spec.Names.Kind}" did not exist.[/]""");
- break;
- }
- }
- catch (HttpOperationException)
- {
- console.WriteLine(
- $"""[red]There was a http (api) error while uninstalling "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]""");
- throw;
- }
- catch (Exception)
- {
- console.WriteLine(
- $"""[red]There was an error while uninstalling "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]""");
- throw;
- }
- }
- }
-}
+using System.CommandLine;
+using System.CommandLine.Invocation;
+
+using k8s;
+using k8s.Autorest;
+using k8s.Models;
+
+using KubeOps.Cli.Transpilation;
+using KubeOps.Transpiler;
+
+using Spectre.Console;
+
+namespace KubeOps.Cli.Commands.Management;
+
+internal static class Uninstall
+{
+ public static Command Command
+ {
+ get
+ {
+ var cmd =
+ new Command("uninstall", "Uninstall CRDs from the cluster of the actually selected context.")
+ {
+ Options.Force,
+ Options.SolutionProjectRegex,
+ Options.TargetFramework,
+ Arguments.SolutionOrProjectFile,
+ };
+ cmd.AddAlias("u");
+ cmd.SetHandler(ctx => Handler(
+ AnsiConsole.Console,
+ new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig()),
+ ctx));
+
+ return cmd;
+ }
+ }
+
+ internal static async Task Handler(IAnsiConsole console, IKubernetes client, InvocationContext ctx)
+ {
+ var file = ctx.ParseResult.GetValueForArgument(Arguments.SolutionOrProjectFile);
+ var force = ctx.ParseResult.GetValueForOption(Options.Force);
+
+ var parser = file switch
+ {
+ { Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(console, file),
+ { Extension: ".sln", Exists: true } => await AssemblyLoader.ForSolution(
+ console,
+ file,
+ ctx.ParseResult.GetValueForOption(Options.SolutionProjectRegex),
+ ctx.ParseResult.GetValueForOption(Options.TargetFramework)),
+ { Exists: false } => throw new FileNotFoundException($"The file {file.Name} does not exist."),
+ _ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."),
+ };
+
+ console.WriteLine($"Uninstall CRDs from {file.Name}.");
+ var crds = parser.Transpile(parser.GetEntities()).ToList();
+ if (crds.Count == 0)
+ {
+ console.WriteLine("No CRDs found. Exiting.");
+ ctx.ExitCode = ExitCodes.Success;
+ return;
+ }
+
+ console.WriteLine($"Found {crds.Count} CRDs.");
+ if (!force && !console.Confirm("[red]Should the CRDs be uninstalled?[/]", false))
+ {
+ ctx.ExitCode = ExitCodes.Aborted;
+ return;
+ }
+
+ console.WriteLine($"""Starting uninstall from cluster with url "{client.BaseUri}".""");
+
+ foreach (var crd in crds)
+ {
+ console.MarkupLineInterpolated(
+ $"""Uninstall [cyan]"{crd.Spec.Group}/{crd.Spec.Names.Kind}"[/] from the cluster.""");
+
+ try
+ {
+ switch (await client.ApiextensionsV1.ListCustomResourceDefinitionAsync(
+ fieldSelector: $"metadata.name={crd.Name()}"))
+ {
+ case { Items: [var existing] }:
+ await client.ApiextensionsV1.DeleteCustomResourceDefinitionAsync(existing.Name());
+ console.MarkupLineInterpolated(
+ $"""[green]CRD "{crd.Spec.Group}/{crd.Spec.Names.Kind}" deleted.[/]""");
+ break;
+ default:
+ console.MarkupLineInterpolated(
+ $"""[green]CRD "{crd.Spec.Group}/{crd.Spec.Names.Kind}" did not exist.[/]""");
+ break;
+ }
+ }
+ catch (HttpOperationException)
+ {
+ console.WriteLine(
+ $"""[red]There was a http (api) error while uninstalling "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]""");
+ throw;
+ }
+ catch (Exception)
+ {
+ console.WriteLine(
+ $"""[red]There was an error while uninstalling "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]""");
+ throw;
+ }
+ }
+ }
+}
diff --git a/src/KubeOps.Cli/Commands/Utilities/Version.cs b/src/KubeOps.Cli/Commands/Utilities/Version.cs
index 5fc70cb9..a22b5fb7 100644
--- a/src/KubeOps.Cli/Commands/Utilities/Version.cs
+++ b/src/KubeOps.Cli/Commands/Utilities/Version.cs
@@ -1,40 +1,40 @@
-using System.CommandLine;
-
-using k8s;
-
-using Spectre.Console;
-
-namespace KubeOps.Cli.Commands.Utilities;
-
-internal static class Version
-{
- public static Command Command
- {
- get
- {
- var cmd = new Command(
- "api-version",
- "Prints the actual server version of the connected kubernetes cluster.");
- cmd.AddAlias("av");
- cmd.SetHandler(() =>
- Handler(AnsiConsole.Console, new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig())));
-
- return cmd;
- }
- }
-
- internal static async Task Handler(IAnsiConsole console, IKubernetes client)
- {
- var version = await client.Version.GetCodeAsync();
- console.Write(new Table()
- .Title("Kubernetes API Version")
- .HideHeaders()
- .AddColumns("Info", "Value")
- .AddRow("Git-Version", version.GitVersion)
- .AddRow("Major", version.Major)
- .AddRow("Minor", version.Minor)
- .AddRow("Platform", version.Platform));
-
- return ExitCodes.Success;
- }
-}
+using System.CommandLine;
+
+using k8s;
+
+using Spectre.Console;
+
+namespace KubeOps.Cli.Commands.Utilities;
+
+internal static class Version
+{
+ public static Command Command
+ {
+ get
+ {
+ var cmd = new Command(
+ "api-version",
+ "Prints the actual server version of the connected kubernetes cluster.");
+ cmd.AddAlias("av");
+ cmd.SetHandler(() =>
+ Handler(AnsiConsole.Console, new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig())));
+
+ return cmd;
+ }
+ }
+
+ internal static async Task Handler(IAnsiConsole console, IKubernetes client)
+ {
+ var version = await client.Version.GetCodeAsync();
+ console.Write(new Table()
+ .Title("Kubernetes API Version")
+ .HideHeaders()
+ .AddColumns("Info", "Value")
+ .AddRow("Git-Version", version.GitVersion)
+ .AddRow("Major", version.Major)
+ .AddRow("Minor", version.Minor)
+ .AddRow("Platform", version.Platform));
+
+ return ExitCodes.Success;
+ }
+}
diff --git a/src/KubeOps.Cli/ExitCodes.cs b/src/KubeOps.Cli/ExitCodes.cs
index 0a6c912c..f3caf944 100644
--- a/src/KubeOps.Cli/ExitCodes.cs
+++ b/src/KubeOps.Cli/ExitCodes.cs
@@ -1,9 +1,9 @@
-namespace KubeOps.Cli;
-
-internal static class ExitCodes
-{
- public const int Success = 0;
- public const int Error = 1;
- public const int Aborted = 2;
- public const int UsageError = 99;
-}
+namespace KubeOps.Cli;
+
+internal static class ExitCodes
+{
+ public const int Success = 0;
+ public const int Error = 1;
+ public const int Aborted = 2;
+ public const int UsageError = 99;
+}
diff --git a/src/KubeOps.Cli/Generators/CertificateGenerator.cs b/src/KubeOps.Cli/Generators/CertificateGenerator.cs
index f0edc328..101a5c0e 100644
--- a/src/KubeOps.Cli/Generators/CertificateGenerator.cs
+++ b/src/KubeOps.Cli/Generators/CertificateGenerator.cs
@@ -1,23 +1,23 @@
-using KubeOps.Cli.Certificates;
-using KubeOps.Cli.Output;
-
-namespace KubeOps.Cli.Generators;
-
-internal class CertificateGenerator(string serverName, string namespaceName) : IConfigGenerator
-{
- public void Generate(ResultOutput output)
- {
- var (caCert, caKey) = Certificates.CertificateGenerator.CreateCaCertificate();
-
- output.Add("ca.pem", caCert.ToPem(), OutputFormat.Plain);
- output.Add("ca-key.pem", caKey.ToPem(), OutputFormat.Plain);
-
- var (srvCert, srvKey) = Certificates.CertificateGenerator.CreateServerCertificate(
- (caCert, caKey),
- serverName,
- namespaceName);
-
- output.Add("svc.pem", srvCert.ToPem(), OutputFormat.Plain);
- output.Add("svc-key.pem", srvKey.ToPem(), OutputFormat.Plain);
- }
-}
+using KubeOps.Cli.Certificates;
+using KubeOps.Cli.Output;
+
+namespace KubeOps.Cli.Generators;
+
+internal class CertificateGenerator(string serverName, string namespaceName) : IConfigGenerator
+{
+ public void Generate(ResultOutput output)
+ {
+ var (caCert, caKey) = Certificates.CertificateGenerator.CreateCaCertificate();
+
+ output.Add("ca.pem", caCert.ToPem(), OutputFormat.Plain);
+ output.Add("ca-key.pem", caKey.ToPem(), OutputFormat.Plain);
+
+ var (srvCert, srvKey) = Certificates.CertificateGenerator.CreateServerCertificate(
+ (caCert, caKey),
+ serverName,
+ namespaceName);
+
+ output.Add("svc.pem", srvCert.ToPem(), OutputFormat.Plain);
+ output.Add("svc-key.pem", srvKey.ToPem(), OutputFormat.Plain);
+ }
+}
diff --git a/src/KubeOps.Cli/Generators/CrdGenerator.cs b/src/KubeOps.Cli/Generators/CrdGenerator.cs
index 1a9572f8..129a6a0f 100644
--- a/src/KubeOps.Cli/Generators/CrdGenerator.cs
+++ b/src/KubeOps.Cli/Generators/CrdGenerator.cs
@@ -1,46 +1,46 @@
-using System.Reflection;
-
-using k8s.Models;
-
-using KubeOps.Cli.Output;
-using KubeOps.Cli.Transpilation;
-using KubeOps.Transpiler;
-
-namespace KubeOps.Cli.Generators;
-
-internal class CrdGenerator(MetadataLoadContext parser, byte[] caBundle,
- OutputFormat outputFormat) : IConfigGenerator
-{
- public void Generate(ResultOutput output)
- {
- var crds = parser.Transpile(parser.GetEntities()).ToList();
- var conversionWebhooks = parser.GetConvertedEntities().ToList();
-
- foreach (var crd in crds)
- {
- if (conversionWebhooks
- .Find(wh => crd.Spec.Group == wh.Group && crd.Spec.Names.Kind == wh.Kind) is not null)
- {
- crd.Spec.Conversion = new V1CustomResourceConversion
- {
- Strategy = "Webhook",
- Webhook = new V1WebhookConversion
- {
- ConversionReviewVersions = new[] { "v1" },
- ClientConfig = new Apiextensionsv1WebhookClientConfig
- {
- CaBundle = caBundle,
- Service = new Apiextensionsv1ServiceReference
- {
- Path = $"/convert/{crd.Spec.Group}/{crd.Spec.Names.Plural}",
- Name = "service",
- },
- },
- },
- };
- }
-
- output.Add($"{crd.Metadata.Name.Replace('.', '_')}.{outputFormat.GetFileExtension()}", crd);
- }
- }
-}
+using System.Reflection;
+
+using k8s.Models;
+
+using KubeOps.Cli.Output;
+using KubeOps.Cli.Transpilation;
+using KubeOps.Transpiler;
+
+namespace KubeOps.Cli.Generators;
+
+internal class CrdGenerator(MetadataLoadContext parser, byte[] caBundle,
+ OutputFormat outputFormat) : IConfigGenerator
+{
+ public void Generate(ResultOutput output)
+ {
+ var crds = parser.Transpile(parser.GetEntities()).ToList();
+ var conversionWebhooks = parser.GetConvertedEntities().ToList();
+
+ foreach (var crd in crds)
+ {
+ if (conversionWebhooks
+ .Find(wh => crd.Spec.Group == wh.Group && crd.Spec.Names.Kind == wh.Kind) is not null)
+ {
+ crd.Spec.Conversion = new V1CustomResourceConversion
+ {
+ Strategy = "Webhook",
+ Webhook = new V1WebhookConversion
+ {
+ ConversionReviewVersions = new[] { "v1" },
+ ClientConfig = new Apiextensionsv1WebhookClientConfig
+ {
+ CaBundle = caBundle,
+ Service = new Apiextensionsv1ServiceReference
+ {
+ Path = $"/convert/{crd.Spec.Group}/{crd.Spec.Names.Plural}",
+ Name = "service",
+ },
+ },
+ },
+ };
+ }
+
+ output.Add($"{crd.Metadata.Name.Replace('.', '_')}.{outputFormat.GetFileExtension()}", crd);
+ }
+ }
+}
diff --git a/src/KubeOps.Cli/Generators/DeploymentGenerator.cs b/src/KubeOps.Cli/Generators/DeploymentGenerator.cs
index a976ee01..f8741960 100644
--- a/src/KubeOps.Cli/Generators/DeploymentGenerator.cs
+++ b/src/KubeOps.Cli/Generators/DeploymentGenerator.cs
@@ -1,69 +1,69 @@
-using k8s;
-using k8s.Models;
-
-using KubeOps.Cli.Output;
-
-namespace KubeOps.Cli.Generators;
-
-internal class DeploymentGenerator(OutputFormat format) : IConfigGenerator
-{
- public void Generate(ResultOutput output)
- {
- var deployment = new V1Deployment(metadata: new V1ObjectMeta(
- labels: new Dictionary { { "operator-deployment", "kubernetes-operator" } },
- name: "operator")).Initialize();
- deployment.Spec = new V1DeploymentSpec
- {
- Replicas = 1,
- RevisionHistoryLimit = 0,
- Selector = new V1LabelSelector(
- matchLabels: new Dictionary { { "operator-deployment", "kubernetes-operator" } }),
- Template = new V1PodTemplateSpec
- {
- Metadata = new V1ObjectMeta(
- labels: new Dictionary { { "operator-deployment", "kubernetes-operator" } }),
- Spec = new V1PodSpec
- {
- TerminationGracePeriodSeconds = 10,
- Containers = new List
- {
- new()
- {
- Image = "operator",
- Name = "operator",
- Env = new List
- {
- new()
- {
- Name = "POD_NAMESPACE",
- ValueFrom =
- new V1EnvVarSource
- {
- FieldRef = new V1ObjectFieldSelector
- {
- FieldPath = "metadata.namespace",
- },
- },
- },
- },
- Resources = new V1ResourceRequirements
- {
- Requests = new Dictionary
- {
- { "cpu", new ResourceQuantity("100m") },
- { "memory", new ResourceQuantity("64Mi") },
- },
- Limits = new Dictionary
- {
- { "cpu", new ResourceQuantity("100m") },
- { "memory", new ResourceQuantity("128Mi") },
- },
- },
- },
- },
- },
- },
- };
- output.Add($"deployment.{format.GetFileExtension()}", deployment);
- }
-}
+using k8s;
+using k8s.Models;
+
+using KubeOps.Cli.Output;
+
+namespace KubeOps.Cli.Generators;
+
+internal class DeploymentGenerator(OutputFormat format) : IConfigGenerator
+{
+ public void Generate(ResultOutput output)
+ {
+ var deployment = new V1Deployment(metadata: new V1ObjectMeta(
+ labels: new Dictionary { { "operator-deployment", "kubernetes-operator" } },
+ name: "operator")).Initialize();
+ deployment.Spec = new V1DeploymentSpec
+ {
+ Replicas = 1,
+ RevisionHistoryLimit = 0,
+ Selector = new V1LabelSelector(
+ matchLabels: new Dictionary { { "operator-deployment", "kubernetes-operator" } }),
+ Template = new V1PodTemplateSpec
+ {
+ Metadata = new V1ObjectMeta(
+ labels: new Dictionary { { "operator-deployment", "kubernetes-operator" } }),
+ Spec = new V1PodSpec
+ {
+ TerminationGracePeriodSeconds = 10,
+ Containers = new List
+ {
+ new()
+ {
+ Image = "operator",
+ Name = "operator",
+ Env = new List
+ {
+ new()
+ {
+ Name = "POD_NAMESPACE",
+ ValueFrom =
+ new V1EnvVarSource
+ {
+ FieldRef = new V1ObjectFieldSelector
+ {
+ FieldPath = "metadata.namespace",
+ },
+ },
+ },
+ },
+ Resources = new V1ResourceRequirements
+ {
+ Requests = new Dictionary