diff --git a/.editorconfig b/.editorconfig index dece05a..667d8fb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,13 +1,24 @@ -# top-most EditorConfig file +# Remove the line below if you want to inherit .editorconfig settings from higher directories root = true -# Don't use tabs for indentation. +#### Core EditorConfig Options #### [*] + +# Global indentation indent_style = space + +# New line preferences +end_of_line = crlf +insert_final_newline = true +trim_trailing_whitespace = true + +# Guidelines guidelines = 100 -# (Please don't specify an indent_size here; that has too many unintended consequences.) +guidelines_style = 2.5px solid 40ff0000 +# Indentation # Code files + [*.{cs,csx,vb,vbx}] indent_size = 4 @@ -23,54 +34,264 @@ indent_size = 2 [*.json] indent_size = 2 -# Dotnet code style settings: -[*.{cs,vb}] -# Sort using and Import directives with System.* appearing first -dotnet_sort_system_directives_first = true -# Avoid "this." and "Me." if not necessary -dotnet_style_qualification_for_field = false:suggestion -dotnet_style_qualification_for_property = false:suggestion -dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_event = false:suggestion - -# Use language keywords instead of framework type names for type references -dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion -dotnet_style_predefined_type_for_member_access = true:suggestion - -# Suggest more modern language features when available -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion - -# CSharp code style settings: +#### Genereated Code flags #### +[src/Ops.DataProvider.SqlServer.Ops/Migrations/*] +generated_code = true + +[src/Ops.DataProvider.SqlServer.Promenade/Migrations/*] +generated_code = true + +#### .NET Code Actions #### [*.cs] -# Prefer `var` where obvious -csharp_style_var_when_type_is_apparent = true:suggestion - -# Prefer method-like constructs to have a block body -csharp_style_expression_bodied_methods = false:none -csharp_style_expression_bodied_constructors = false:none -csharp_style_expression_bodied_operators = false:none - -# Prefer property-like constructs to have an expression-body -csharp_style_expression_bodied_properties = true:none -csharp_style_expression_bodied_indexers = true:none -csharp_style_expression_bodied_accessors = true:none - -# Suggest more modern language features when available -csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion -csharp_style_throw_expression = true:suggestion -csharp_style_conditional_delegate_call = true:suggestion - -# Newline settings -csharp_new_line_before_open_brace = all -csharp_new_line_before_else = true + +# Type members +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties + +# Symbol search +dotnet_search_reference_assemblies = true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = true +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_non_hidden_explicit_cast_in_source = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = true + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true +csharp_prefer_system_threading_lock = true +csharp_style_namespace_declarations = block_scoped +csharp_style_prefer_method_group_conversion = true +csharp_style_prefer_primary_constructors = true +csharp_style_prefer_simple_property_accessors = true +csharp_style_prefer_top_level_statements = true + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_implicitly_typed_lambda_expression = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_unbound_generic_type_in_nameof = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### 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_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true -insert_final_newline=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 #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.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 = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +# Diagnostic rules migrated from ruleset + +# XML comment analysis is disabled due to project configuration +dotnet_diagnostic.SA0001.severity = none + +# Prefix local calls with this +dotnet_diagnostic.SA1101.severity = none + +# TODO turn this back on once most of the project is refactored +# Split parameters should start on line after declaration +dotnet_diagnostic.SA1116.severity = none + +# Using directives should be placed correctly +dotnet_diagnostic.SA1200.severity = none + +# TODO turn this back on once everything is documented +# Elements should be documented +dotnet_diagnostic.SA1600.severity = none + +# File should have header +dotnet_diagnostic.SA1633.severity = none diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 33c3135..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: "Build - action" - -on: [push, pull_request] - -jobs: - build-docker-image: - name: "ImageOptimApi Docker" - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Create environment file - run: echo NUGET_API_KEY=${{ secrets.NUGET_API_KEY }} >> release.env - - - name: Run build.bash - env: - BLD_DOCKER_IMAGE: imageoptimapi - CR_OWNER: ${{secrets.CR_OWNER}} - CR_PASSWORD: ${{secrets.CR_PASSWORD}} - CR_USER: ${{secrets.CR_USER}} - GHCR_OWNER: ${{secrets.GHCR_OWNER}} - GHCR_PAT: ${{secrets.GHCR_PAT}} - GHCR_USER: ${{secrets.GHCR_USER}} - run: ./build.bash -p - - - uses: actions/upload-artifact@v3 - with: - name: ImageOptimApi.nupkg - path: package/ImageOptimApi.*.nupkg - if-no-files-found: ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4f660cd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +name: ci +run-name: ${{ github.actor }} is running ci + +on: [push, pull_request] + +jobs: + build-docker-image: + name: "ImageOptimApi Container" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + + - name: Run build.bash + env: + BLD_DOCKER_IMAGE: imageoptimapi + CR_OWNER: ${{secrets.CR_OWNER}} + CR_PASSWORD: ${{secrets.CR_PASSWORD}} + CR_USER: ${{secrets.CR_USER}} + GHCR_OWNER: ${{secrets.GHCR_OWNER}} + GHCR_PAT: ${{secrets.GHCR_PAT}} + GHCR_USER: ${{secrets.GHCR_USER}} + run: ./build.bash -p + + - uses: actions/upload-artifact@v7 + with: + name: ImageOptimApi.nupkg + path: package/ImageOptimApi.*.nupkg + if-no-files-found: ignore + + push-nupkg-to-nuget: + if: contains( github.ref, 'release' ) + name: "Push ImageOptimApi Nupkg" + permissions: + id-token: write + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + + - name: Run build.bash + env: + BLD_DOCKER_IMAGE: imageoptimapi + CR_OWNER: ${{secrets.CR_OWNER}} + CR_PASSWORD: ${{secrets.CR_PASSWORD}} + CR_USER: ${{secrets.CR_USER}} + GHCR_OWNER: ${{secrets.GHCR_OWNER}} + GHCR_PAT: ${{secrets.GHCR_PAT}} + GHCR_USER: ${{secrets.GHCR_USER}} + run: ./build.bash -p + + - uses: actions/upload-artifact@v7 + with: + name: ImageOptimApi.nupkg + path: package/ImageOptimApi.*.nupkg + if-no-files-found: ignore + + # Get a short-lived NuGet API key + - name: NuGet login (OIDC → temp API key) + uses: NuGet/login@v1 + id: login + with: + user: ${{ secrets.NUGET_USER }} + + # Push the package + - name: NuGet push + run: dotnet nuget push package/ImageOptimApi.*.nupkg --api-key ${{steps.login.outputs.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json + diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 1a2c926..b04a5b6 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -1,9 +1,8 @@ -name: "Packages - action" +name: dotnet outdated +run-name: ${{ github.actor }} is running dotnet outdated on: pull_request: - schedule: - - cron: '0 5 * * 5' jobs: dotnet-outdated: @@ -11,6 +10,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - - uses: mcld/dotnet-outdated-action@v3 + - uses: mcld/dotnet-outdated-action@v4.1 diff --git a/.sa1201ierrc b/.sa1201ierrc new file mode 100644 index 0000000..7cfa256 --- /dev/null +++ b/.sa1201ierrc @@ -0,0 +1,36 @@ +{ + "alphabeticalSort": true, + "sortTopLevelTypes": true, + "staticMembersFirst": true, + "constMembersFirst": true, + "insertBlankLineBetweenMembers": false, + "accessLevelOrder": [ + "Public", + "Internal", + "ProtectedInternal", + "Protected", + "PrivateProtected", + "Private" + ], + "memberTypeOrder": [ + "Field", + "Constructor", + "Destructor", + "Delegate", + "Event", + "Enum", + "Interface", + "Property", + "Indexer", + "Method", + "Struct", + "Class" + ], + "topLevelTypeOrder": [ + "Enum", + "Interface", + "Struct", + "Class" + ], + "_comment": "SA1201ier configuration file. See documentation for available options." +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 51f9384..a2b9b4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.2.0] + +### Added + +- Ability to work on an image byte stream + +### Changed + +- Updated library references to .NET 8.0 +- Improved build.bash script + ## [1.1.0] ### Changed @@ -17,5 +28,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Everything, initial release +[1.2.0]: https://github.com/MCLD/dotnet-imageoptim-api/releases/tag/v1.2.0 [1.1.0]: https://github.com/MCLD/dotnet-imageoptim-api/releases/tag/v1.1.0 [1.0.0]: https://github.com/MCLD/dotnet-imageoptim-api/releases/tag/v1.0.0 diff --git a/Dockerfile b/Dockerfile index 756634a..c8b7cd3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Get build image -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /app @@ -22,7 +22,7 @@ RUN dotnet pack -c Release ImageOptimApi -o "/app/publish/" RUN cp /app/release-publish.bash "/app/publish/" # Get runtime image -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS publish +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS publish WORKDIR /app @@ -57,3 +57,4 @@ COPY --from=build "/app/publish/" . # Set entrypoint ENTRYPOINT ["/app/release-publish.bash"] + diff --git a/ImageOptimApi.Console/ImageOptimApi.Console.csproj b/ImageOptimApi.Console/ImageOptimApi.Console.csproj index e6f2e29..b355d1e 100644 --- a/ImageOptimApi.Console/ImageOptimApi.Console.csproj +++ b/ImageOptimApi.Console/ImageOptimApi.Console.csproj @@ -12,14 +12,14 @@ MIT https://github.com/MCLD/dotnet-imageoptim-api https://github.com/MCLD/dotnet-imageoptim-api - net7.0 - 1.1.0 + net8.0 + 1.2.0 - - + + diff --git a/ImageOptimApi.Console/OptionException.cs b/ImageOptimApi.Console/OptionException.cs index 5e154b5..23c3b5b 100644 --- a/ImageOptimApi.Console/OptionException.cs +++ b/ImageOptimApi.Console/OptionException.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ImageOptimApi.Console +namespace ImageOptimApi.Console { internal class OptionException : Exception { @@ -16,7 +10,7 @@ public OptionException(string? message) : base(message) { } - public OptionException(string? message, Exception? innerException) + public OptionException(string? message, Exception? innerException) : base(message, innerException) { } diff --git a/ImageOptimApi.Console/Options.cs b/ImageOptimApi.Console/Options.cs index 3dafda9..cd4c301 100644 --- a/ImageOptimApi.Console/Options.cs +++ b/ImageOptimApi.Console/Options.cs @@ -69,4 +69,4 @@ internal class Options HelpText = "Maximum image width. If not specified, the height will vary depending on image's aspect ratio. Dimensions are specified in CSS pixels.")] public int? Width { get; set; } } -} \ No newline at end of file +} diff --git a/ImageOptimApi.Console/Program.cs b/ImageOptimApi.Console/Program.cs index 8f04c31..2e59f6f 100644 --- a/ImageOptimApi.Console/Program.cs +++ b/ImageOptimApi.Console/Program.cs @@ -1,8 +1,8 @@ -using CommandLine; +using System.Net.Http.Headers; +using System.Reflection; +using CommandLine; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using System.Net.Http.Headers; -using System.Reflection; namespace ImageOptimApi.Console { @@ -80,7 +80,7 @@ private static async Task Main(string[] args) var title = Assembly .GetEntryAssembly()? .GetCustomAttribute()? - .Title ?? nameof(ImageOptimApi) + '.' + nameof(ImageOptimApi.Console); + .Title ?? (nameof(ImageOptimApi) + '.' + nameof(ImageOptimApi.Console)); var version = Assembly .GetEntryAssembly()? .GetCustomAttribute()? @@ -96,7 +96,8 @@ private static async Task Main(string[] args) .ConfigureHttpClient(_ => { _.Timeout = TimeSpan.FromSeconds(30); - _.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(title, version)); + _.DefaultRequestHeaders.UserAgent + .Add(new ProductInfoHeaderValue(title, version)); }); _provider = services.BuildServiceProvider(); @@ -194,11 +195,13 @@ private static async Task RunOptionsAsync(Options options) { if (string.IsNullOrEmpty(inputFilePath)) { - System.Console.WriteLine($"Optimized {inputFilename} to {outputFilename} {result.File.Length:N0} bytes in {result.ElapsedSeconds:N0}s"); + System.Console.WriteLine( + $"Optimized {inputFilename} to {outputFilename} {result.File.Length:N0} bytes in {result.ElapsedSeconds:N0}s"); } else { - System.Console.WriteLine($"Optimized {inputFilename} to {outputFilename} from {result.OriginalSize:N0} to {result.File.Length:N0} bytes in {result.ElapsedSeconds:N0}s"); + System.Console.WriteLine( + $"Optimized {inputFilename} to {outputFilename} from {result.OriginalSize:N0} to {result.File.Length:N0} bytes in {result.ElapsedSeconds:N0}s"); } if (!string.IsNullOrEmpty(options.OutputPath)) @@ -206,7 +209,9 @@ private static async Task RunOptionsAsync(Options options) Directory.CreateDirectory(options.OutputPath); } - await File.WriteAllBytesAsync(Path.Combine(options.OutputPath, outputFilename), result.File); + await File.WriteAllBytesAsync( + Path.Combine(options.OutputPath, outputFilename), + result.File); return 1; } else diff --git a/ImageOptimApi/Client.cs b/ImageOptimApi/Client.cs index eb1c155..417ed0f 100644 --- a/ImageOptimApi/Client.cs +++ b/ImageOptimApi/Client.cs @@ -1,5 +1,3 @@ -using Microsoft.AspNetCore.StaticFiles; -using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; @@ -8,6 +6,8 @@ using System.Net.Http.Headers; using System.Reflection; using System.Threading.Tasks; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Logging; namespace ImageOptimApi { @@ -50,26 +50,6 @@ public Client(HttpClient httpClient, ILogger logger) public string Username { get; set; } public int? Width { get; set; } - public Task OptimizeAsync(Uri imageUri) - { - if (imageUri == null || string.IsNullOrEmpty(imageUri.AbsoluteUri)) - { - throw new ArgumentNullException(nameof(imageUri)); - } - - return OptimizeImageInternalAsync(null, imageUri); - } - - public Task OptimizeAsync(string path) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - return OptimizeImageInternalAsync(path, null); - } - private static string ConvertCropType(CropType cropType) => cropType switch { CropType.Auto => "crop=auto", @@ -97,8 +77,37 @@ public Task OptimizeAsync(string path) _ => "medium" }; - private async Task OptimizeImageInternalAsync(string path, Uri imageUri) + public Task OptimizeAsync(Uri imageUri) { + return imageUri == null || string.IsNullOrEmpty(imageUri.AbsoluteUri) + ? throw new ArgumentNullException(nameof(imageUri)) + : OptimizeImageInternalAsync(null, imageUri, null, null); + } + + public Task OptimizeAsync(string path) + { + return string.IsNullOrEmpty(path) + ? throw new ArgumentNullException(nameof(path)) + : OptimizeImageInternalAsync(path, null, null, null); + } + + public Task OptimizeAsync(byte[] imageBytes, string filename) + { + return OptimizeImageInternalAsync(null, null, imageBytes, filename); + } + + private async Task OptimizeImageInternalAsync(string path, + Uri imageUri, + byte[] imageBytes, + string imageFilename) + { + if (string.IsNullOrWhiteSpace(path) + && imageUri == null + && (imageBytes == null || string.IsNullOrWhiteSpace(imageFilename))) + { + throw new ArgumentException("You must supply an iamge path, an image URI, or image bytes along with a filename."); + } + var sw = Stopwatch.StartNew(); _logger.LogDebug("Preparing parameters"); var urlPath = PrepareParameters(_httpClient.Timeout); @@ -111,25 +120,38 @@ private async Task OptimizeImageInternalAsync(string path, string contentType = null; Uri requestUri; - if (string.IsNullOrEmpty(path)) + if (imageUri != null) { requestUri = new Uri(BaseAddress + urlPath + imageUri); _logger.LogDebug("Using image from URL, image path: {ImagePath}", imageUri); } else { - if (!System.IO.File.Exists(path)) + if (!string.IsNullOrEmpty(path)) { - throw new ParameterException($"File not found: {path}"); + if (!System.IO.File.Exists(path)) + { + throw new ParameterException($"File not found: {path}"); + } + filename = System.IO.Path.GetFileName(path); + var provider = new FileExtensionContentTypeProvider(); + if (!provider.TryGetContentType(filename, out contentType)) + { + throw new ParameterException($"Unable to determine type of file: {filename}"); + } + _logger.LogDebug("Reading file at path: {ImagePath}", path); + image = await System.IO.File.ReadAllBytesAsync(path); } - filename = System.IO.Path.GetFileName(path); - var provider = new FileExtensionContentTypeProvider(); - if (!provider.TryGetContentType(filename, out contentType)) + else { - throw new ParameterException($"Unable to determine type of file: {filename}"); + filename = imageFilename; + var provider = new FileExtensionContentTypeProvider(); + if (!provider.TryGetContentType(filename, out contentType)) + { + throw new ParameterException($"Unable to determine type of file: {filename}"); + } + image = imageBytes; } - _logger.LogDebug("Reading file at path: {ImagePath}", path); - image = await System.IO.File.ReadAllBytesAsync(path); optimized.OriginalSize = image.Length; requestUri = new Uri(BaseAddress + urlPath); } @@ -141,7 +163,7 @@ private async Task OptimizeImageInternalAsync(string path, { if (image != null) { - form = new MultipartFormDataContent(); + form = []; byteContent = new ByteArrayContent(image); byteContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType); form.Add(byteContent, name: "file", fileName: filename); @@ -174,7 +196,7 @@ private async Task OptimizeImageInternalAsync(string path, if (DisableWebCall) { - optimized.File = image ?? new byte[] { 0xff }; + optimized.File = image ?? [0xff]; optimized.Status = Status.TestSuccess; optimized.StatusMessage = "Test call, file is not an optimized message."; sw.Stop(); @@ -317,7 +339,7 @@ private string PrepareParameters(TimeSpan timeout) options.Add($"timeout={timeout.TotalSeconds}"); } - return string.Join("/", new[] { Username, string.Join(",", options) }) + '/'; + return string.Join("/", [Username, string.Join(",", options)]) + '/'; } private void VerifyParameters() diff --git a/ImageOptimApi/CropType.cs b/ImageOptimApi/CropType.cs index e767ad5..1455d6d 100644 --- a/ImageOptimApi/CropType.cs +++ b/ImageOptimApi/CropType.cs @@ -9,4 +9,4 @@ public enum CropType Right, Bottom } -} \ No newline at end of file +} diff --git a/ImageOptimApi/Format.cs b/ImageOptimApi/Format.cs index 4db24ec..f2864cd 100644 --- a/ImageOptimApi/Format.cs +++ b/ImageOptimApi/Format.cs @@ -8,4 +8,4 @@ public enum Format WebM, H264 } -} \ No newline at end of file +} diff --git a/ImageOptimApi/HighDpi.cs b/ImageOptimApi/HighDpi.cs index 9fcb4f6..46c7774 100644 --- a/ImageOptimApi/HighDpi.cs +++ b/ImageOptimApi/HighDpi.cs @@ -6,4 +6,4 @@ public enum HighDpi Dpi2x, Dpi3x } -} \ No newline at end of file +} diff --git a/ImageOptimApi/ImageOptimApi.csproj b/ImageOptimApi/ImageOptimApi.csproj index b4e00fc..5c66bad 100644 --- a/ImageOptimApi/ImageOptimApi.csproj +++ b/ImageOptimApi/ImageOptimApi.csproj @@ -16,7 +16,7 @@ https://github.com/MCLD/dotnet-imageoptim-api netstandard2.1 ImageOptim API - 1.1.0 + 1.2.0 @@ -24,8 +24,8 @@ - - + + diff --git a/ImageOptimApi/OptimizedImageResult.cs b/ImageOptimApi/OptimizedImageResult.cs index 281b58d..9541a7b 100644 --- a/ImageOptimApi/OptimizedImageResult.cs +++ b/ImageOptimApi/OptimizedImageResult.cs @@ -14,4 +14,4 @@ public class OptimizedImageResult public string ViaHeader { get; set; } public IEnumerable Warnings { get; set; } } -} \ No newline at end of file +} diff --git a/ImageOptimApi/ParameterException.cs b/ImageOptimApi/ParameterException.cs index 536f95a..243a67a 100644 --- a/ImageOptimApi/ParameterException.cs +++ b/ImageOptimApi/ParameterException.cs @@ -18,4 +18,4 @@ public ParameterException(string message, Exception innerException) { } } -} \ No newline at end of file +} diff --git a/ImageOptimApi/Quality.cs b/ImageOptimApi/Quality.cs index ef8f6f0..bc94c38 100644 --- a/ImageOptimApi/Quality.cs +++ b/ImageOptimApi/Quality.cs @@ -7,4 +7,4 @@ public enum Quality High, Lossless } -} \ No newline at end of file +} diff --git a/ImageOptimApi/README.md b/ImageOptimApi/README.md index d6ab74e..9f1ebac 100644 --- a/ImageOptimApi/README.md +++ b/ImageOptimApi/README.md @@ -100,6 +100,11 @@ When you need the `ImageOptimApi` client you can obtain it from the `ServiceProv You can then configure and use the client to optimize your images. +## Version History + +- v1.2.0 - .NET 8.0 - adds the ability to work on an image provided as a byte array +- v1.1.0 - .NET 7.0 + ## License The ImageOptimApi source code is Copyright 2022 by the Maricopa County Library District and is distributed under [The MIT License](http://opensource.org/licenses/MIT). diff --git a/ImageOptimApi/Status.cs b/ImageOptimApi/Status.cs index 26ac9d5..7ae7f2e 100644 --- a/ImageOptimApi/Status.cs +++ b/ImageOptimApi/Status.cs @@ -10,4 +10,4 @@ public enum Status TestSuccess, UsernameMissingIncorrect } -} \ No newline at end of file +} diff --git a/build.bash b/build.bash index 6e5ac1c..e88cc26 100755 --- a/build.bash +++ b/build.bash @@ -12,8 +12,6 @@ set -Eeuo pipefail trap cleanup SIGINT SIGTERM ERR EXIT -script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P) - usage() { cat <&2 -e "${1-}" + echo >&2 -e "${1-}" } die() { - local msg=$1 - local code=${2-1} # default exit status 1 - msg "$msg" - exit "$code" + local msg=$1 + local code=${2-1} # default exit status 1 + msg "$msg" + exit "$code" } parse_params() { - dockerfile='' - publish=0 - - while :; do - case "${1-}" in - -h | --help) usage ;; - -v | --verbose) set -x ;; - --no-color) NO_COLOR=1 ;; - -df | --dockerfile) - dockerfile="${2-}" - shift - ;; - -p | --publish) - publish=1 - ;; - -?*) die "Unknown option: $1" ;; - *) break ;; - esac - shift - done - - readonly dockertag="${1-}" - - return 0 + dockerfile='' + publish=0 + + while :; do + case "${1-}" in + -h | --help) usage ;; + -v | --verbose) set -x ;; + --no-color) NO_COLOR=1 ;; + -df | --dockerfile) + dockerfile="${2-}" + shift + ;; + -p | --publish) + publish=1 + ;; + -?*) die "Unknown option: $1" ;; + *) break ;; + esac + shift + done + + readonly dockertag="${1-}" + + return 0 } parse_params "$@" @@ -103,6 +104,7 @@ readonly BLD_COMMIT BLD_VERSION_DATE=$(date -u +'%Y%m%d_%H%M%SZ') readonly BLD_VERSION_DATE +BLD_LATEST=false BLD_PUSH=false BLD_RELEASE=false BLD_VERSION=unknown @@ -110,68 +112,105 @@ BLD_RELEASE_VERSION='' readonly BLD_STARTAT=$SECONDS +SYSARCH=$(arch) +readonly SYSARCH + +if [[ ${SYSARCH} = "i386" ]]; then + readonly ARCH="x86_32" +elif [[ ${SYSARCH} = "aarch64" ]]; then + readonly ARCH="armv7" +else + readonly ARCH=${SYSARCH} +fi + +OS=$(uname -s) +readonly OS + +if [[ -z ${DOCKER_LOCK_VERSION-} && -f "docker-lock-version.txt" ]]; then + BLD_DOCKER_LOCK_VERSION=$(cat docker-lock-version.txt) + readonly BLD_DOCKER_LOCK_VERSION +else + BLD_DOCKER_LOCK_VERSION=${DOCKER_LOCK_VERSION-} + readonly BLD_DOCKER_LOCK_VERSION +fi + +if [[ -n ${BLD_DOCKER_LOCK_VERSION-} && -f "docker-lock.json" ]]; then + readonly DOCKER_LOCK_URL="https://github.com/safe-waters/docker-lock/releases/download/v${BLD_DOCKER_LOCK_VERSION}/docker-lock_${BLD_DOCKER_LOCK_VERSION}_${OS}_${ARCH}.tar.gz" + msg "${BLUE}===${NOFORMAT} Using docker-lock.json version ${BLD_DOCKER_LOCK_VERSION} to pin Dockerfile(s)" + mkdir -p ".docker/cli-plugins" + curl -fsSL "${DOCKER_LOCK_URL}" | tar -xz -C ".docker/cli-plugins" "docker-lock" + chmod +x ".docker/cli-plugins/docker-lock" + .docker/cli-plugins/docker-lock lock rewrite +fi + # Try getting branch from Azure DevOps readonly AZURE_BRANCH=${BUILD_SOURCEBRANCH-} BLD_BRANCH='' BLD_GITBRANCH=${AZURE_BRANCH#refs/heads/} if [[ -n $BLD_GITBRANCH ]]; then - BLD_BRANCH=$BLD_GITBRANCH - msg "${BLUE}===${NOFORMAT} Branch from Azure DevOps: $BLD_BRANCH" + BLD_BRANCH=$BLD_GITBRANCH + msg "${BLUE}===${NOFORMAT} Branch from Azure DevOps: $BLD_BRANCH" fi if [[ -z ${BLD_BRANCH} ]]; then - # Try getting branch from git - if BLD_GITBRANCH=$(git symbolic-ref --short -q HEAD); then - BLD_BRANCH=$BLD_GITBRANCH - msg "${BLUE}===${NOFORMAT} Branch from git: $BLD_BRANCH" - fi + # Try getting branch from git + if BLD_GITBRANCH=$(git symbolic-ref --short -q HEAD); then + BLD_BRANCH=$BLD_GITBRANCH + msg "${BLUE}===${NOFORMAT} Branch from git: $BLD_BRANCH" + fi fi if [[ $BLD_BRANCH = "develop" - || $BLD_BRANCH = "main" - || $BLD_BRANCH = "master" - || $BLD_BRANCH = "test" ]]; then - BLD_DOCKER_TAG=$BLD_BRANCH - BLD_VERSION=${BLD_BRANCH}-${BLD_VERSION_DATE} - BLD_PUSH=true + || $BLD_BRANCH = "main" + || $BLD_BRANCH = "master" + || $BLD_BRANCH = "test" ]]; then + BLD_DOCKER_TAG=$BLD_BRANCH + BLD_VERSION=${BLD_BRANCH}-${BLD_VERSION_DATE} + BLD_PUSH=true elif [[ "$BLD_BRANCH" =~ release/([0-9]+\.[0-9]+\.[0-9]+.*) ]]; then - BLD_RELEASE_VERSION=${BASH_REMATCH[1]} - BLD_DOCKER_TAG=v${BLD_RELEASE_VERSION} - BLD_VERSION=v${BLD_RELEASE_VERSION} - BLD_RELEASE=true - BLD_PUSH=true + BLD_RELEASE_VERSION=${BASH_REMATCH[1]} + BLD_DOCKER_TAG=v${BLD_RELEASE_VERSION} + BLD_VERSION=v${BLD_RELEASE_VERSION} + BLD_RELEASE=true + BLD_PUSH=true else - BLD_DOCKER_TAG=$BLD_COMMIT - BLD_VERSION=${BLD_COMMIT}-${BLD_VERSION_DATE} + BLD_DOCKER_TAG=$BLD_COMMIT + BLD_VERSION=${BLD_COMMIT}-${BLD_VERSION_DATE} +fi + +# check if this is latest +if [[ $BLD_BRANCH = "main" + || $BLD_BRANCH = "master" ]]; then + BLD_LATEST=true fi # Check if we should use a different Dockerfile if [[ -z $dockerfile ]]; then - BLD_DOCKERFILE="Dockerfile" + BLD_DOCKERFILE="Dockerfile" else - BLD_DOCKERFILE="$dockerfile" - msg "${BLUE}===${NOFORMAT} Using Dockerfile: $BLD_DOCKERFILE" + BLD_DOCKERFILE="$dockerfile" + msg "${BLUE}===${NOFORMAT} Using Dockerfile: $BLD_DOCKERFILE" fi # Ensure a configured Docker image name if [[ -z ${BLD_DOCKER_IMAGE-} ]]; then - BLD_DIRECTORY=${PWD##*/} - BLD_DOCKER_IMAGE=${BLD_DIRECTORY,,} - msg "${ORANGE}===${NOFORMAT} No BLD_DOCKER_IMAGE configured, using this directory name: $BLD_DOCKER_IMAGE" + BLD_DIRECTORY=${PWD##*/} + BLD_DOCKER_IMAGE=${BLD_DIRECTORY,,} + msg "${ORANGE}===${NOFORMAT} No BLD_DOCKER_IMAGE configured, using this directory name: $BLD_DOCKER_IMAGE" fi # Perform release prep if necessary and script is present -if [[ $BLD_RELEASE = "true" && -f "release-prep.bash" ]]; then - msg "${BLUE}===${NOFORMAT} Running release preparation for version $BLD_RELEASE_VERSION" - #shellcheck disable=SC1091 - source release-prep.bash - msg "${GREEN}===${NOFORMAT} Release preparation script complete" +if [[ $BLD_RELEASE = "true" && -x "release-prep.bash" ]]; then + msg "${BLUE}===${NOFORMAT} Running release preparation for version $BLD_RELEASE_VERSION" + #shellcheck disable=SC1091 + source release-prep.bash + msg "${GREEN}===${NOFORMAT} Release preparation script complete" fi # If there's a specified Docker tag, use it if [[ -n $dockertag ]]; then - BLD_DOCKER_TAG="$dockertag" - msg "${BLUE}===${NOFORMAT} Using specified Docker tag $BLD_DOCKER_TAG" + BLD_DOCKER_TAG="$dockertag" + msg "${BLUE}===${NOFORMAT} Using specified Docker tag $BLD_DOCKER_TAG" fi # Construct complete Docker image and tag from provided values @@ -181,119 +220,119 @@ BLD_FULL_DOCKER_IMAGE=${BLD_DOCKER_IMAGE} BLD_FULL_DOCKER_LATEST=${BLD_DOCKER_LATEST} if [[ -n ${CR_OWNER-} ]]; then - msg "${BLUE}===${NOFORMAT} Adding container registry owner $CR_OWNER" - BLD_FULL_DOCKER_IMAGE=${CR_OWNER}/${BLD_FULL_DOCKER_IMAGE} - BLD_FULL_DOCKER_LATEST=${CR_OWNER}/${BLD_FULL_DOCKER_LATEST} + msg "${BLUE}===${NOFORMAT} Adding container registry owner $CR_OWNER" + BLD_FULL_DOCKER_IMAGE=${CR_OWNER}/${BLD_FULL_DOCKER_IMAGE} + BLD_FULL_DOCKER_LATEST=${CR_OWNER}/${BLD_FULL_DOCKER_LATEST} fi if [[ -n ${CR_HOST-} ]]; then - msg "${BLUE}===${NOFORMAT} Adding container registry host $CR_HOST" - BLD_FULL_DOCKER_IMAGE=${CR_HOST}/${BLD_FULL_DOCKER_IMAGE} - BLD_FULL_DOCKER_LATEST=${CR_HOST}/${BLD_FULL_DOCKER_LATEST} + msg "${BLUE}===${NOFORMAT} Adding container registry host $CR_HOST" + BLD_FULL_DOCKER_IMAGE=${CR_HOST}/${BLD_FULL_DOCKER_IMAGE} + BLD_FULL_DOCKER_LATEST=${CR_HOST}/${BLD_FULL_DOCKER_LATEST} fi msg "${BLUE}===${NOFORMAT} Building branch $BLD_BRANCH commit $BLD_COMMIT as Docker image $BLD_FULL_DOCKER_IMAGE" msg "${BLUE}===${NOFORMAT} Image version: $BLD_VERSION" if [[ $BLD_PUSH = true ]]; then - docker build -f "$BLD_DOCKERFILE" -t "$BLD_FULL_DOCKER_IMAGE" \ - --build-arg BRANCH="$BLD_BRANCH" \ - --build-arg IMAGE_CREATED="$BLD_VERSION_DATE" \ - --build-arg IMAGE_REVISION="$BLD_COMMIT" \ - --build-arg IMAGE_VERSION="$BLD_VERSION" . - - msg "${GREEN}===${NOFORMAT} Docker image built" - - dockeruser=${CR_USER-} - dockerpass=${CR_PASSWORD-} - - if [[ -z $dockeruser || -z $dockerpass ]]; then - msg "${ORANGE}===${NOFORMAT} Not pushing Docker image: username or password not specified" - else - msg "${BLUE}===${NOFORMAT} Authenticating..." - if [[ -z ${CR_HOST-} ]]; then - echo "$dockerpass" | \ - docker login -u "$dockeruser" --password-stdin || exit $? - else - echo "$dockerpass" | \ - docker login -u "$dockeruser" --password-stdin "${CR_HOST-}" || exit $? - fi - - msg "${BLUE}===${NOFORMAT} Pushing image $BLD_FULL_DOCKER_IMAGE" - docker push "$BLD_FULL_DOCKER_IMAGE" - - if [[ $BLD_RELEASE = "true" ]]; then - msg "${BLUE}===${NOFORMAT} Tagging and pushing $BLD_FULL_DOCKER_LATEST" - docker tag "$BLD_FULL_DOCKER_IMAGE" "$BLD_FULL_DOCKER_LATEST" - docker push "$BLD_FULL_DOCKER_LATEST" - fi - - msg "${GREEN}===${NOFORMAT} Docker image pushed" - - msg "${BLUE}===${NOFORMAT} Executing logout" - if [[ -z ${CR_HOST-} ]]; then - docker logout + docker build -f "$BLD_DOCKERFILE" -t "$BLD_FULL_DOCKER_IMAGE" \ + --build-arg BRANCH="$BLD_BRANCH" \ + --build-arg IMAGE_CREATED="$BLD_VERSION_DATE" \ + --build-arg IMAGE_REVISION="$BLD_COMMIT" \ + --build-arg IMAGE_VERSION="$BLD_VERSION" . + + msg "${GREEN}===${NOFORMAT} Docker image built" + + dockeruser=${CR_USER-} + dockerpass=${CR_PASSWORD-} + + if [[ -z $dockeruser || -z $dockerpass ]]; then + msg "${ORANGE}===${NOFORMAT} Not pushing Docker image: username or password not specified" else - docker logout "${CR_HOST-}" + msg "${BLUE}===${NOFORMAT} Authenticating..." + if [[ -z ${CR_HOST-} ]]; then + echo "$dockerpass" | \ + docker login -u "$dockeruser" --password-stdin || exit $? + else + echo "$dockerpass" | \ + docker login -u "$dockeruser" --password-stdin "${CR_HOST-}" || exit $? + fi + + msg "${BLUE}===${NOFORMAT} Pushing image $BLD_FULL_DOCKER_IMAGE" + docker push "$BLD_FULL_DOCKER_IMAGE" + + if [[ $BLD_LATEST = "true" ]]; then + msg "${BLUE}===${NOFORMAT} Tagging and pushing $BLD_FULL_DOCKER_LATEST" + docker tag "$BLD_FULL_DOCKER_IMAGE" "$BLD_FULL_DOCKER_LATEST" + docker push "$BLD_FULL_DOCKER_LATEST" + fi + + msg "${GREEN}===${NOFORMAT} Docker image pushed" + + msg "${BLUE}===${NOFORMAT} Executing logout" + if [[ -z ${CR_HOST-} ]]; then + docker logout + else + docker logout "${CR_HOST-}" + fi fi - fi - - ghcruser=${GHCR_USER-} - - if [[ -n $ghcruser ]]; then - ghcrowner=${GHCR_OWNER-} - if [[ -z $ghcrowner ]]; then - ghcrowner=$ghcruser + + ghcruser=${GHCR_USER-} + + if [[ -n $ghcruser ]]; then + ghcrowner=${GHCR_OWNER-} + if [[ -z $ghcrowner ]]; then + ghcrowner=$ghcruser + fi + msg "${BLUE}===${NOFORMAT} Pushing image to ghcr.io/${ghcrowner}/${BLD_DOCKER_IMAGE}" + docker tag "${BLD_FULL_DOCKER_IMAGE}" "ghcr.io/${ghcrowner}/${BLD_DOCKER_IMAGE}" + echo "$GHCR_PAT" | \ + docker login ghcr.io -u "${ghcruser}" --password-stdin || exit $? + docker push "ghcr.io/${ghcrowner}/${BLD_DOCKER_IMAGE}" + + if [[ $BLD_LATEST = "true" ]]; then + msg "${BLUE}===${NOFORMAT} Tagging and pushing ghcr.io/${ghcrowner}/${BLD_DOCKER_LATEST}" + docker tag "${BLD_FULL_DOCKER_IMAGE}" "ghcr.io/${ghcrowner}/${BLD_DOCKER_LATEST}" + docker push "ghcr.io/${ghcrowner}/${BLD_DOCKER_LATEST}" + fi + + docker logout ghcr.io + + msg "${GREEN}===${NOFORMAT} Docker image pushed to ghcr.io" fi - msg "${BLUE}===${NOFORMAT} Pushing image to ghcr.io/${ghcrowner}/${BLD_DOCKER_IMAGE}" - docker tag "${BLD_FULL_DOCKER_IMAGE}" "ghcr.io/${ghcrowner}/${BLD_DOCKER_IMAGE}" - echo "$GHCR_PAT" | \ - docker login ghcr.io -u "${ghcruser}" --password-stdin || exit $? - docker push "ghcr.io/${ghcrowner}/${BLD_DOCKER_IMAGE}" - - if [[ $BLD_RELEASE = "true" ]]; then - msg "${BLUE}===${NOFORMAT} Tagging and pushing ghcr.io/${ghcrowner}/${BLD_DOCKER_LATEST}" - docker tag "${BLD_FULL_DOCKER_IMAGE}" "ghcr.io/${ghcrowner}/${BLD_DOCKER_LATEST}" - docker push "ghcr.io/${ghcrowner}/${BLD_DOCKER_LATEST}" - fi - - docker logout ghcr.io - - msg "${GREEN}===${NOFORMAT} Docker image pushed to ghcr.io" - fi - - # Perform release publish in the Docker machine if configuration is present - - if [[ $BLD_RELEASE = "true" && -f "release-publish.bash" && publish -eq 1 ]]; then - msg "${BLUE}===${NOFORMAT} Publishing release package for $BLD_RELEASE_VERSION" - mkdir -p publish - if [[ -f "release.env" ]]; then - docker run -i \ - --rm \ - --entrypoint "/app/release-publish.bash" \ - --env-file release.env \ - -e BLD_RELEASE_VERSION="$BLD_RELEASE_VERSION" \ - -v "${PWD}/package:/package" \ - "$BLD_FULL_DOCKER_IMAGE" - else - docker run -i \ - --rm \ - --entrypoint "/app/release-publish.bash" \ - -e BLD_RELEASE_VERSION="$BLD_RELEASE_VERSION" \ - -v "${PWD}/package:/package" \ - "$BLD_FULL_DOCKER_IMAGE" + + # Perform release publish in the Docker machine if configuration is present + + if [[ $BLD_RELEASE = "true" && -x "release-publish.bash" && publish -eq 1 ]]; then + msg "${BLUE}===${NOFORMAT} Publishing release package for $BLD_RELEASE_VERSION" + mkdir -p publish + if [[ -f "release.env" ]]; then + docker run -i \ + --rm \ + --entrypoint "/app/release-publish.bash" \ + --env-file release.env \ + -e BLD_RELEASE_VERSION="$BLD_RELEASE_VERSION" \ + -v "${PWD}/package:/package" \ + "$BLD_FULL_DOCKER_IMAGE" + else + docker run -i \ + --rm \ + --entrypoint "/app/release-publish.bash" \ + -e BLD_RELEASE_VERSION="$BLD_RELEASE_VERSION" \ + -v "${PWD}/package:/package" \ + "$BLD_FULL_DOCKER_IMAGE" + fi + msg "${GREEN}===${NOFORMAT} Publish script complete" fi - msg "${GREEN}===${NOFORMAT} Publish script complete" - fi else - docker build -f "$BLD_DOCKERFILE" -t "$BLD_FULL_DOCKER_IMAGE" \ - --build-arg BRANCH="$BLD_BRANCH" \ - --build-arg IMAGE_CREATED="$BLD_VERSION_DATE" \ - --build-arg IMAGE_REVISION="$BLD_COMMIT" \ - --build-arg IMAGE_VERSION="$BLD_VERSION" \ - --target build . - msg "${GREEN}===${NOFORMAT} Docker image built" - msg "${ORANGE}===${NOFORMAT} Not pushing Docker image: branch is not develop, main, test, or versioned release" + docker build -f "$BLD_DOCKERFILE" -t "$BLD_FULL_DOCKER_IMAGE" \ + --build-arg BRANCH="$BLD_BRANCH" \ + --build-arg IMAGE_CREATED="$BLD_VERSION_DATE" \ + --build-arg IMAGE_REVISION="$BLD_COMMIT" \ + --build-arg IMAGE_VERSION="$BLD_VERSION" \ + --target build . + msg "${GREEN}===${NOFORMAT} Docker image built" + msg "${ORANGE}===${NOFORMAT} Not pushing Docker image: branch is not develop, main, test, or versioned release" fi msg "${PURPLE}===${NOFORMAT} Build script complete in $((SECONDS - BLD_STARTAT)) seconds."