From 566f2a2b9044fc13f899f9a1453713126d865d34 Mon Sep 17 00:00:00 2001 From: ADITYA 123 Date: Sun, 7 Apr 2024 12:43:29 +0530 Subject: [PATCH 01/10] Added cartservice code --- .gitignore | 3 + 1 | 1 - cartservice.sln | 48 ++++++++ src/.dockerignore | 6 + src/Dockerfile | 40 +++++++ src/Dockerfile.debug | 37 ++++++ src/Program.cs | 26 ++++ src/Startup.cs | 84 +++++++++++++ src/appsettings.json | 15 +++ src/cartservice.csproj | 21 ++++ src/cartstore/AlloyDBCartStore.cs | 170 ++++++++++++++++++++++++++ src/cartstore/ICartStore.cs | 26 ++++ src/cartstore/RedisCartStore.cs | 118 ++++++++++++++++++ src/cartstore/SpannerCartStore.cs | 185 +++++++++++++++++++++++++++++ src/protos/Cart.proto | 50 ++++++++ src/services/CartService.cs | 51 ++++++++ src/services/HealthCheckService.cs | 41 +++++++ tests/.gitignore | 3 + tests/CartServiceTests.cs | 160 +++++++++++++++++++++++++ tests/cartservice.tests.csproj | 20 ++++ 20 files changed, 1104 insertions(+), 1 deletion(-) create mode 100644 .gitignore delete mode 100644 1 create mode 100644 cartservice.sln create mode 100644 src/.dockerignore create mode 100644 src/Dockerfile create mode 100644 src/Dockerfile.debug create mode 100644 src/Program.cs create mode 100644 src/Startup.cs create mode 100644 src/appsettings.json create mode 100644 src/cartservice.csproj create mode 100644 src/cartstore/AlloyDBCartStore.cs create mode 100644 src/cartstore/ICartStore.cs create mode 100644 src/cartstore/RedisCartStore.cs create mode 100644 src/cartstore/SpannerCartStore.cs create mode 100644 src/protos/Cart.proto create mode 100644 src/services/CartService.cs create mode 100644 src/services/HealthCheckService.cs create mode 100644 tests/.gitignore create mode 100644 tests/CartServiceTests.cs create mode 100644 tests/cartservice.tests.csproj diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..234c5756 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +**/bin/ +**/obj/ +.vs/*.* diff --git a/1 b/1 deleted file mode 100644 index d00491fd..00000000 --- a/1 +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/cartservice.sln b/cartservice.sln new file mode 100644 index 00000000..8d320825 --- /dev/null +++ b/cartservice.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cartservice", "src\cartservice.csproj", "{2348C29F-E8D3-4955-916D-D609CBC97FCB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cartservice.tests", "tests\cartservice.tests.csproj", "{59825342-CE64-4AFA-8744-781692C0811B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x64.ActiveCfg = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x64.Build.0 = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x86.ActiveCfg = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Debug|x86.Build.0 = Debug|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|Any CPU.Build.0 = Release|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x64.ActiveCfg = Release|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x64.Build.0 = Release|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x86.ActiveCfg = Release|Any CPU + {2348C29F-E8D3-4955-916D-D609CBC97FCB}.Release|x86.Build.0 = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x64.ActiveCfg = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x64.Build.0 = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x86.ActiveCfg = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Debug|x86.Build.0 = Debug|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|Any CPU.Build.0 = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|x64.ActiveCfg = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|x64.Build.0 = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|x86.ActiveCfg = Release|Any CPU + {59825342-CE64-4AFA-8744-781692C0811B}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 00000000..0224086e --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,6 @@ +**/*.sh +**/*.bat +**/bin/ +**/obj/ +**/out/ +Dockerfile* \ No newline at end of file diff --git a/src/Dockerfile b/src/Dockerfile new file mode 100644 index 00000000..458cd1bb --- /dev/null +++ b/src/Dockerfile @@ -0,0 +1,40 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# https://mcr.microsoft.com/v2/dotnet/sdk/tags/list +FROM mcr.microsoft.com/dotnet/sdk:7.0.302@sha256:5c638e77052b5ae4f6f1da3885035b510fc379d2ce4be274c70679114bcdb936 as builder +WORKDIR /app +COPY cartservice.csproj . +RUN dotnet restore cartservice.csproj -r linux-musl-x64 +COPY . . +RUN dotnet publish cartservice.csproj -p:PublishSingleFile=true -r linux-musl-x64 --self-contained true -p:PublishTrimmed=True -p:TrimMode=Link -c release -o /cartservice --no-restore + +# https://mcr.microsoft.com/v2/dotnet/runtime-deps/tags/list +FROM mcr.microsoft.com/dotnet/runtime-deps:7.0.4-alpine3.16-amd64@sha256:7141eea9c7be5f4d2f09df427ba37620e50be150fc93015288b3e26c5071af81 as without-grpc-health-probe-bin + +WORKDIR /app +COPY --from=builder /cartservice . +EXPOSE 7070 +ENV DOTNET_EnableDiagnostics=0 \ + ASPNETCORE_URLS=http://*:7070 +USER 1000 +ENTRYPOINT ["/app/cartservice"] + +FROM without-grpc-health-probe-bin +USER root +# renovate: datasource=github-releases depName=grpc-ecosystem/grpc-health-probe +ENV GRPC_HEALTH_PROBE_VERSION=v0.4.18 +RUN wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ + chmod +x /bin/grpc_health_probe +USER 1000 \ No newline at end of file diff --git a/src/Dockerfile.debug b/src/Dockerfile.debug new file mode 100644 index 00000000..68fc9036 --- /dev/null +++ b/src/Dockerfile.debug @@ -0,0 +1,37 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM mcr.microsoft.com/dotnet/sdk:7.0@sha256:5c638e77052b5ae4f6f1da3885035b510fc379d2ce4be274c70679114bcdb936 AS build +WORKDIR /app +COPY . . +RUN dotnet restore cartservice.csproj +RUN dotnet build "./cartservice.csproj" -c Debug -o /out + +FROM build AS publish +RUN dotnet publish cartservice.csproj -c Debug -o /out + +# Building final image used in running container +FROM mcr.microsoft.com/dotnet/aspnet:7.0@sha256:330ebc778e771002fca7e21a34c3fb3ac2dcfe07b26bdbbeacca34e4beccf1ef AS final +# Installing procps on the container to enable debugging of .NET Core +RUN apt-get update \ + && apt-get install -y unzip procps wget +# renovate: datasource=github-releases depName=grpc-ecosystem/grpc-health-probe +ENV GRPC_HEALTH_PROBE_VERSION=v0.4.18 +RUN wget -qO/bin/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64 && \ + chmod +x /bin/grpc_health_probe +WORKDIR /app +COPY --from=publish /out . +ENV ASPNETCORE_URLS=http://*:7070 + +ENTRYPOINT ["dotnet", "cartservice.dll"] diff --git a/src/Program.cs b/src/Program.cs new file mode 100644 index 00000000..beb7e796 --- /dev/null +++ b/src/Program.cs @@ -0,0 +1,26 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using cartservice; + +CreateHostBuilder(args).Build().Run(); + +static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); \ No newline at end of file diff --git a/src/Startup.cs b/src/Startup.cs new file mode 100644 index 00000000..136d3036 --- /dev/null +++ b/src/Startup.cs @@ -0,0 +1,84 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using cartservice.cartstore; +using cartservice.services; +using Microsoft.Extensions.Caching.StackExchangeRedis; + +namespace cartservice +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + string redisAddress = Configuration["REDIS_ADDR"]; + string spannerProjectId = Configuration["SPANNER_PROJECT"]; + string spannerConnectionString = Configuration["SPANNER_CONNECTION_STRING"]; + string alloyDBConnectionString = Configuration["ALLOYDB_PRIMARY_IP"]; + + if (!string.IsNullOrEmpty(redisAddress)) + { + services.AddStackExchangeRedisCache(options => + { + options.Configuration = redisAddress; + }); + services.AddSingleton(); + } + else if (!string.IsNullOrEmpty(spannerProjectId) || !string.IsNullOrEmpty(spannerConnectionString)) + { + services.AddSingleton(); + } + else if (!string.IsNullOrEmpty(alloyDBConnectionString)) + { + Console.WriteLine("Creating AlloyDB cart store"); + services.AddSingleton(); + } + else + { + Console.WriteLine("Redis cache host(hostname+port) was not specified. Starting a cart service using in memory store"); + services.AddDistributedMemoryCache(); + services.AddSingleton(); + } + + + services.AddGrpc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapGrpcService(); + endpoints.MapGrpcService(); + + endpoints.MapGet("/", async context => + { + await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); + }); + }); + } + } +} diff --git a/src/appsettings.json b/src/appsettings.json new file mode 100644 index 00000000..02e5e60e --- /dev/null +++ b/src/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + } + } \ No newline at end of file diff --git a/src/cartservice.csproj b/src/cartservice.csproj new file mode 100644 index 00000000..a28ca921 --- /dev/null +++ b/src/cartservice.csproj @@ -0,0 +1,21 @@ + + + + net7.0 + + + + + + + + + + + + + + + + + diff --git a/src/cartstore/AlloyDBCartStore.cs b/src/cartstore/AlloyDBCartStore.cs new file mode 100644 index 00000000..9a2c394a --- /dev/null +++ b/src/cartstore/AlloyDBCartStore.cs @@ -0,0 +1,170 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Grpc.Core; +using Npgsql; +using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; +using Google.Api.Gax.ResourceNames; +using Google.Cloud.SecretManager.V1; + +namespace cartservice.cartstore +{ + public class AlloyDBCartStore : ICartStore + { + private readonly string tableName; + private readonly string connectionString; + + public AlloyDBCartStore(IConfiguration configuration) + { + // Create a Cloud Secrets client. + SecretManagerServiceClient client = SecretManagerServiceClient.Create(); + var projectId = configuration["PROJECT_ID"]; + var secretId = configuration["ALLOYDB_SECRET_NAME"]; + SecretVersionName secretVersionName = new SecretVersionName(projectId, secretId, "latest"); + + AccessSecretVersionResponse result = client.AccessSecretVersion(secretVersionName); + // Convert the payload to a string. Payloads are bytes by default. + string alloyDBPassword = result.Payload.Data.ToStringUtf8().TrimEnd('\r', '\n'); + + // TODO: Create a separate user for connecting within the application + // rather than using our superuser + string alloyDBUser = "postgres"; + string databaseName = configuration["ALLOYDB_DATABASE_NAME"]; + // TODO: Consider splitting workloads into read vs. write and take + // advantage of the AlloyDB read pools + string primaryIPAddress = configuration["ALLOYDB_PRIMARY_IP"]; + connectionString = "Host=" + + primaryIPAddress + + ";Username=" + + alloyDBUser + + ";Password=" + + alloyDBPassword + + ";Database=" + + databaseName; + + tableName = configuration["ALLOYDB_TABLE_NAME"]; + } + + + public async Task AddItemAsync(string userId, string productId, int quantity) + { + Console.WriteLine($"AddItemAsync for {userId} called"); + try + { + await using var dataSource = NpgsqlDataSource.Create(connectionString); + + // Fetch the current quantity for our userId/productId tuple + var fetchCmd = $"SELECT quantity FROM {tableName} WHERE userID='{userId}' AND productID='{productId}'"; + var currentQuantity = 0; + var cmdRead = dataSource.CreateCommand(fetchCmd); + await using (var reader = await cmdRead.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + currentQuantity += reader.GetInt32(0); + } + var totalQuantity = quantity + currentQuantity; + + var insertCmd = $"INSERT INTO {tableName} (userId, productId, quantity) VALUES ('{userId}', '{productId}', {totalQuantity})"; + await using (var cmdInsert = dataSource.CreateCommand(insertCmd)) + { + await Task.Run(() => + { + return cmdInsert.ExecuteNonQueryAsync(); + }); + } + } + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {connectionString}. {ex}")); + } + } + + + public async Task GetCartAsync(string userId) + { + Console.WriteLine($"GetCartAsync called for userId={userId}"); + Hipstershop.Cart cart = new(); + cart.UserId = userId; + try + { + await using var dataSource = NpgsqlDataSource.Create(connectionString); + + var cartFetchCmd = $"SELECT productId, quantity FROM {tableName} WHERE userId = '{userId}'"; + var cmd = dataSource.CreateCommand(cartFetchCmd); + await using (var reader = await cmd.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + Hipstershop.CartItem item = new() + { + ProductId = reader.GetString(0), + Quantity = reader.GetInt32(1) + }; + cart.Items.Add(item); + } + } + await Task.Run(() => + { + return cart; + }); + } + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {connectionString}. {ex}")); + } + return cart; + } + + + public async Task EmptyCartAsync(string userId) + { + Console.WriteLine($"EmptyCartAsync called for userId={userId}"); + + try + { + await using var dataSource = NpgsqlDataSource.Create(connectionString); + var deleteCmd = $"DELETE FROM {tableName} WHERE userID = '{userId}'"; + await using (var cmd = dataSource.CreateCommand(deleteCmd)) + { + await Task.Run(() => + { + return cmd.ExecuteNonQueryAsync(); + }); + } + } + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {connectionString}. {ex}")); + } + } + + public bool Ping() + { + try + { + return true; + } + catch (Exception) + { + return false; + } + } + } +} + diff --git a/src/cartstore/ICartStore.cs b/src/cartstore/ICartStore.cs new file mode 100644 index 00000000..ccf01e26 --- /dev/null +++ b/src/cartstore/ICartStore.cs @@ -0,0 +1,26 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Threading.Tasks; + +namespace cartservice.cartstore +{ + public interface ICartStore + { + Task AddItemAsync(string userId, string productId, int quantity); + Task EmptyCartAsync(string userId); + Task GetCartAsync(string userId); + bool Ping(); + } +} \ No newline at end of file diff --git a/src/cartstore/RedisCartStore.cs b/src/cartstore/RedisCartStore.cs new file mode 100644 index 00000000..3c835b20 --- /dev/null +++ b/src/cartstore/RedisCartStore.cs @@ -0,0 +1,118 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Grpc.Core; +using Microsoft.Extensions.Caching.Distributed; +using Google.Protobuf; + +namespace cartservice.cartstore +{ + public class RedisCartStore : ICartStore + { + private readonly IDistributedCache _cache; + + public RedisCartStore(IDistributedCache cache) + { + _cache = cache; + } + + public async Task AddItemAsync(string userId, string productId, int quantity) + { + Console.WriteLine($"AddItemAsync called with userId={userId}, productId={productId}, quantity={quantity}"); + + try + { + Hipstershop.Cart cart; + var value = await _cache.GetAsync(userId); + if (value == null) + { + cart = new Hipstershop.Cart(); + cart.UserId = userId; + cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity }); + } + else + { + cart = Hipstershop.Cart.Parser.ParseFrom(value); + var existingItem = cart.Items.SingleOrDefault(i => i.ProductId == productId); + if (existingItem == null) + { + cart.Items.Add(new Hipstershop.CartItem { ProductId = productId, Quantity = quantity }); + } + else + { + existingItem.Quantity += quantity; + } + } + await _cache.SetAsync(userId, cart.ToByteArray()); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}")); + } + } + + public async Task EmptyCartAsync(string userId) + { + Console.WriteLine($"EmptyCartAsync called with userId={userId}"); + + try + { + var cart = new Hipstershop.Cart(); + await _cache.SetAsync(userId, cart.ToByteArray()); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}")); + } + } + + public async Task GetCartAsync(string userId) + { + Console.WriteLine($"GetCartAsync called with userId={userId}"); + + try + { + // Access the cart from the cache + var value = await _cache.GetAsync(userId); + + if (value != null) + { + return Hipstershop.Cart.Parser.ParseFrom(value); + } + + // We decided to return empty cart in cases when user wasn't in the cache before + return new Hipstershop.Cart(); + } + catch (Exception ex) + { + throw new RpcException(new Status(StatusCode.FailedPrecondition, $"Can't access cart storage. {ex}")); + } + } + + public bool Ping() + { + try + { + return true; + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/src/cartstore/SpannerCartStore.cs b/src/cartstore/SpannerCartStore.cs new file mode 100644 index 00000000..a97e53c9 --- /dev/null +++ b/src/cartstore/SpannerCartStore.cs @@ -0,0 +1,185 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using Google.Cloud.Spanner.Data; +using Grpc.Core; +using Microsoft.Extensions.Configuration; +using System.Threading.Tasks; + +namespace cartservice.cartstore +{ + public class SpannerCartStore : ICartStore + { + private static readonly string TableName = "CartItems"; + private static readonly string DefaultInstanceName = "onlineboutique"; + private static readonly string DefaultDatabaseName = "carts"; + private readonly string databaseString; + + public SpannerCartStore(IConfiguration configuration) + { + string spannerProjectId = configuration["SPANNER_PROJECT"]; + string spannerInstanceId = configuration["SPANNER_INSTANCE"]; + string spannerDatabaseId = configuration["SPANNER_DATABASE"]; + string spannerConnectionString = configuration["SPANNER_CONNECTION_STRING"]; + SpannerConnectionStringBuilder builder = new(); + if (!string.IsNullOrEmpty(spannerConnectionString)) { + builder.DataSource = spannerConnectionString; + databaseString = builder.ToString(); + Console.WriteLine($"Spanner connection string: ${databaseString}"); + return; + } + if (string.IsNullOrEmpty(spannerInstanceId)) + spannerInstanceId = DefaultInstanceName; + if (string.IsNullOrEmpty(spannerDatabaseId)) + spannerDatabaseId = DefaultDatabaseName; + builder.DataSource = + $"projects/{spannerProjectId}/instances/{spannerInstanceId}/databases/{spannerDatabaseId}"; + databaseString = builder.ToString(); + Console.WriteLine($"Built Spanner connection string: '{databaseString}'"); + } + + + public async Task AddItemAsync(string userId, string productId, int quantity) + { + Console.WriteLine($"AddItemAsync for {userId} called"); + try + { + using SpannerConnection spannerConnection = new(databaseString); + await spannerConnection.RunWithRetriableTransactionAsync(async transaction => + { + int currentQuantity = 0; + var quantityLookup = spannerConnection.CreateSelectCommand( + $"SELECT * FROM {TableName} WHERE userId = @userId AND productId = @productId", + new SpannerParameterCollection + { + { "userId", SpannerDbType.String }, + { "productId", SpannerDbType.String } + }); + quantityLookup.Parameters["userId"].Value = userId; + quantityLookup.Parameters["productId"].Value = productId; + quantityLookup.Transaction = transaction; + using (var reader = await quantityLookup.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) { + currentQuantity += reader.GetFieldValue("quantity"); + } + } + + var cmd = spannerConnection.CreateInsertOrUpdateCommand(TableName, + new SpannerParameterCollection + { + { "userId", SpannerDbType.String }, + { "productId", SpannerDbType.String }, + { "quantity", SpannerDbType.Int64 } + }); + cmd.Parameters["userId"].Value = userId; + cmd.Parameters["productId"].Value = productId; + cmd.Parameters["quantity"].Value = currentQuantity + quantity; + cmd.Transaction = transaction; + await Task.Run(() => + { + return cmd.ExecuteNonQueryAsync(); + }); + }); + } + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {databaseString}. {ex}")); + } + } + + + public async Task GetCartAsync(string userId) + { + Console.WriteLine($"GetCartAsync called for userId={userId}"); + Hipstershop.Cart cart = new(); + try + { + using SpannerConnection spannerConnection = new(databaseString); + var cmd = spannerConnection.CreateSelectCommand( + $"SELECT * FROM {TableName} WHERE userId = @userId", + new SpannerParameterCollection { + { "userId", SpannerDbType.String } + } + ); + cmd.Parameters["userId"].Value = userId; + using var reader = await cmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) + { + // Only add the userId if something is in the cart. + // This is based on how the cartservice example behaves. + // An empty cart has no userId attached. + cart.UserId = userId; + + Hipstershop.CartItem item = new() + { + ProductId = reader.GetFieldValue("productId"), + Quantity = reader.GetFieldValue("quantity") + }; + cart.Items.Add(item); + } + + return cart; + } + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {databaseString}. {ex}")); + } + } + + + public async Task EmptyCartAsync(string userId) + { + Console.WriteLine($"EmptyCartAsync called for userId={userId}"); + + try + { + using SpannerConnection spannerConnection = new(databaseString); + await Task.Run(() => + { + var cmd = spannerConnection.CreateDmlCommand( + $"DELETE FROM {TableName} WHERE userId = @userId", + new SpannerParameterCollection + { + { "userId", SpannerDbType.String } + }); + cmd.Parameters["userId"].Value = userId; + return cmd.ExecuteNonQueryAsync(); + }); + } + + catch (Exception ex) + { + throw new RpcException( + new Status(StatusCode.FailedPrecondition, $"Can't access cart storage at {databaseString}. {ex}")); + } + } + + public bool Ping() + { + try + { + return true; + } + catch (Exception) + { + return false; + } + } + } +} + diff --git a/src/protos/Cart.proto b/src/protos/Cart.proto new file mode 100644 index 00000000..b2974cb6 --- /dev/null +++ b/src/protos/Cart.proto @@ -0,0 +1,50 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package hipstershop; + +// -----------------Cart service----------------- + +service CartService { + rpc AddItem(AddItemRequest) returns (Empty) {} + rpc GetCart(GetCartRequest) returns (Cart) {} + rpc EmptyCart(EmptyCartRequest) returns (Empty) {} +} + +message CartItem { + string product_id = 1; + int32 quantity = 2; +} + +message AddItemRequest { + string user_id = 1; + CartItem item = 2; +} + +message EmptyCartRequest { + string user_id = 1; +} + +message GetCartRequest { + string user_id = 1; +} + +message Cart { + string user_id = 1; + repeated CartItem items = 2; +} + +message Empty {} diff --git a/src/services/CartService.cs b/src/services/CartService.cs new file mode 100644 index 00000000..7edff1c6 --- /dev/null +++ b/src/services/CartService.cs @@ -0,0 +1,51 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Threading.Tasks; +using Grpc.Core; +using Microsoft.Extensions.Logging; +using cartservice.cartstore; +using Hipstershop; + +namespace cartservice.services +{ + public class CartService : Hipstershop.CartService.CartServiceBase + { + private readonly static Empty Empty = new Empty(); + private readonly ICartStore _cartStore; + + public CartService(ICartStore cartStore) + { + _cartStore = cartStore; + } + + public async override Task AddItem(AddItemRequest request, ServerCallContext context) + { + await _cartStore.AddItemAsync(request.UserId, request.Item.ProductId, request.Item.Quantity); + return Empty; + } + + public override Task GetCart(GetCartRequest request, ServerCallContext context) + { + return _cartStore.GetCartAsync(request.UserId); + } + + public async override Task EmptyCart(EmptyCartRequest request, ServerCallContext context) + { + await _cartStore.EmptyCartAsync(request.UserId); + return Empty; + } + } +} \ No newline at end of file diff --git a/src/services/HealthCheckService.cs b/src/services/HealthCheckService.cs new file mode 100644 index 00000000..289b0e80 --- /dev/null +++ b/src/services/HealthCheckService.cs @@ -0,0 +1,41 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Health.V1; +using static Grpc.Health.V1.Health; +using cartservice.cartstore; + +namespace cartservice.services +{ + internal class HealthCheckService : HealthBase + { + private ICartStore _cartStore { get; } + + public HealthCheckService (ICartStore cartStore) + { + _cartStore = cartStore; + } + + public override Task Check(HealthCheckRequest request, ServerCallContext context) + { + Console.WriteLine ("Checking CartService Health"); + return Task.FromResult(new HealthCheckResponse { + Status = _cartStore.Ping() ? HealthCheckResponse.Types.ServingStatus.Serving : HealthCheckResponse.Types.ServingStatus.NotServing + }); + } + } +} \ No newline at end of file diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..61aadf7b --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,3 @@ +/bin/* +/obj/* +/.vs/* \ No newline at end of file diff --git a/tests/CartServiceTests.cs b/tests/CartServiceTests.cs new file mode 100644 index 00000000..ee1c7a11 --- /dev/null +++ b/tests/CartServiceTests.cs @@ -0,0 +1,160 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Threading.Tasks; +using Grpc.Net.Client; +using Hipstershop; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Hosting; +using Xunit; +using static Hipstershop.CartService; + +namespace cartservice.tests +{ + public class CartServiceTests + { + private readonly IHostBuilder _host; + + public CartServiceTests() + { + _host = new HostBuilder().ConfigureWebHost(webBuilder => + { + webBuilder + .UseStartup() + .UseTestServer(); + }); + } + + [Fact] + public async Task GetItem_NoAddItemBefore_EmptyCartReturned() + { + // Setup test server and client + using var server = await _host.StartAsync(); + var httpClient = server.GetTestClient(); + + string userId = Guid.NewGuid().ToString(); + + // Create a GRPC communication channel between the client and the server + var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions + { + HttpClient = httpClient + }); + + var cartClient = new CartServiceClient(channel); + + var request = new GetCartRequest + { + UserId = userId, + }; + + var cart = await cartClient.GetCartAsync(request); + Assert.NotNull(cart); + + // All grpc objects implement IEquitable, so we can compare equality with by-value semantics + Assert.Equal(new Cart(), cart); + } + + [Fact] + public async Task AddItem_ItemExists_Updated() + { + // Setup test server and client + using var server = await _host.StartAsync(); + var httpClient = server.GetTestClient(); + + string userId = Guid.NewGuid().ToString(); + + // Create a GRPC communication channel between the client and the server + var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions + { + HttpClient = httpClient + }); + + var client = new CartServiceClient(channel); + var request = new AddItemRequest + { + UserId = userId, + Item = new CartItem + { + ProductId = "1", + Quantity = 1 + } + }; + + // First add - nothing should fail + await client.AddItemAsync(request); + + // Second add of existing product - quantity should be updated + await client.AddItemAsync(request); + + var getCartRequest = new GetCartRequest + { + UserId = userId + }; + var cart = await client.GetCartAsync(getCartRequest); + Assert.NotNull(cart); + Assert.Equal(userId, cart.UserId); + Assert.Single(cart.Items); + Assert.Equal(2, cart.Items[0].Quantity); + + // Cleanup + await client.EmptyCartAsync(new EmptyCartRequest { UserId = userId }); + } + + [Fact] + public async Task AddItem_New_Inserted() + { + // Setup test server and client + using var server = await _host.StartAsync(); + var httpClient = server.GetTestClient(); + + string userId = Guid.NewGuid().ToString(); + + // Create a GRPC communication channel between the client and the server + var channel = GrpcChannel.ForAddress(httpClient.BaseAddress, new GrpcChannelOptions + { + HttpClient = httpClient + }); + + // Create a proxy object to work with the server + var client = new CartServiceClient(channel); + + var request = new AddItemRequest + { + UserId = userId, + Item = new CartItem + { + ProductId = "1", + Quantity = 1 + } + }; + + await client.AddItemAsync(request); + + var getCartRequest = new GetCartRequest + { + UserId = userId + }; + var cart = await client.GetCartAsync(getCartRequest); + Assert.NotNull(cart); + Assert.Equal(userId, cart.UserId); + Assert.Single(cart.Items); + + await client.EmptyCartAsync(new EmptyCartRequest { UserId = userId }); + cart = await client.GetCartAsync(getCartRequest); + Assert.Empty(cart.Items); + } + } +} diff --git a/tests/cartservice.tests.csproj b/tests/cartservice.tests.csproj new file mode 100644 index 00000000..5072af3e --- /dev/null +++ b/tests/cartservice.tests.csproj @@ -0,0 +1,20 @@ + + + + net7.0 + + false + + + + + + + + + + + + + + From d4f989daf4323f08540f1836538779099085f8b5 Mon Sep 17 00:00:00 2001 From: Aditya Jaiswal <32607172+jaiswaladi246@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:28:23 +0530 Subject: [PATCH 02/10] Create Jenkinsfile --- Jenkinsfile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..6fed7054 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,25 @@ +pipeline { + agent any + + stages { + stage('Build & Tag Docker Image') { + steps { + script { + withDockerRegistry(credentialsId: 'docker-cred') { + sh "docker build -t adijaiswal/cartservice:latest -f src/Dockerfile ." + } + } + } + } + + stage('Push Docker Image') { + steps { + script { + withDockerRegistry(credentialsId: 'docker-cred') { + sh "docker push adijaiswal/cartservice:latest " + } + } + } + } + } +} From 7148e543e2e9bf2bacccff04363099249ca5387c Mon Sep 17 00:00:00 2001 From: Aditya Jaiswal <32607172+jaiswaladi246@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:05:19 +0530 Subject: [PATCH 03/10] Update Jenkinsfile --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 6fed7054..59033bcd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,7 @@ pipeline { stage('Build & Tag Docker Image') { steps { script { - withDockerRegistry(credentialsId: 'docker-cred') { + withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { sh "docker build -t adijaiswal/cartservice:latest -f src/Dockerfile ." } } @@ -15,7 +15,7 @@ pipeline { stage('Push Docker Image') { steps { script { - withDockerRegistry(credentialsId: 'docker-cred') { + withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { sh "docker push adijaiswal/cartservice:latest " } } From 4331056eaf3ba959e831a3d19b0ea8c131d8e2f8 Mon Sep 17 00:00:00 2001 From: Aditya Jaiswal <32607172+jaiswaladi246@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:33:42 +0530 Subject: [PATCH 04/10] Update Jenkinsfile --- Jenkinsfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 59033bcd..aea7c1fb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,9 +5,12 @@ pipeline { stage('Build & Tag Docker Image') { steps { script { + dir('src') { + withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { sh "docker build -t adijaiswal/cartservice:latest -f src/Dockerfile ." } + } } } } From b00c061bdd256d920cb50dce1ea353968c5ef717 Mon Sep 17 00:00:00 2001 From: Aditya Jaiswal <32607172+jaiswaladi246@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:34:39 +0530 Subject: [PATCH 05/10] Update Jenkinsfile --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index aea7c1fb..923da54e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,7 @@ pipeline { dir('src') { withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { - sh "docker build -t adijaiswal/cartservice:latest -f src/Dockerfile ." + sh "docker build -t adijaiswal/cartservice:latest ." } } } From 3f269ffb11f0bd09e0da33b58dbfc8ce2c4152e5 Mon Sep 17 00:00:00 2001 From: Harsh Gupta <141814041+harsh2478@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:08:07 +0530 Subject: [PATCH 06/10] Update Jenkinsfile --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 923da54e..287bc482 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,7 @@ pipeline { dir('src') { withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { - sh "docker build -t adijaiswal/cartservice:latest ." + sh "docker build -t kanah05/cartservice:latest ." } } } @@ -19,7 +19,7 @@ pipeline { steps { script { withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { - sh "docker push adijaiswal/cartservice:latest " + sh "docker push kanha05/cartservice:latest " } } } From cfff2c80c616cb69bafc9c8104a48fa20b1b82ab Mon Sep 17 00:00:00 2001 From: Harsh Gupta <141814041+harsh2478@users.noreply.github.com> Date: Sun, 13 Oct 2024 16:12:32 +0530 Subject: [PATCH 07/10] Update Jenkinsfile --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 287bc482..4580a33f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,7 @@ pipeline { dir('src') { withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { - sh "docker build -t kanah05/cartservice:latest ." + sh "docker build -t kanha05/cartservice:latest ." } } } From 5770ce4505b19c376eb7be1c8682146a79c376d4 Mon Sep 17 00:00:00 2001 From: usubbu Date: Fri, 22 Nov 2024 11:59:46 +0530 Subject: [PATCH 08/10] Update Jenkinsfile --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4580a33f..eed4c5b4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,7 @@ pipeline { dir('src') { withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { - sh "docker build -t kanha05/cartservice:latest ." + sh "docker build -t shaikmustafa/cartservice:latest ." } } } @@ -19,7 +19,7 @@ pipeline { steps { script { withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { - sh "docker push kanha05/cartservice:latest " + sh "docker push shaikmustafa/cartservice:latest " } } } From e4258fd8fbb5471d5d4bb3cd1cb6f82e4d9114e9 Mon Sep 17 00:00:00 2001 From: RohithBabu0987 Date: Sun, 2 Mar 2025 17:21:47 -0600 Subject: [PATCH 09/10] Update Jenkinsfile --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index eed4c5b4..7b843440 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,7 @@ pipeline { dir('src') { withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { - sh "docker build -t shaikmustafa/cartservice:latest ." + sh "docker build -t rohith7799/cartservice:latest ." } } } @@ -19,7 +19,7 @@ pipeline { steps { script { withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { - sh "docker push shaikmustafa/cartservice:latest " + sh "docker push rohith7799/cartservice:latest " } } } From 36eab62bfd5f70c125df419baa2a7067da599238 Mon Sep 17 00:00:00 2001 From: RohithBabu0987 Date: Sun, 2 Mar 2025 17:52:54 -0600 Subject: [PATCH 10/10] Update Jenkinsfile --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7b843440..6774fc20 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,7 @@ pipeline { dir('src') { withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { - sh "docker build -t rohith7799/cartservice:latest ." + sh "docker build -t rohithbabu7799/cartservice:latest ." } } } @@ -19,7 +19,7 @@ pipeline { steps { script { withDockerRegistry(credentialsId: 'docker-cred', toolName: 'docker') { - sh "docker push rohith7799/cartservice:latest " + sh "docker push rohithbabu7799/cartservice:latest " } } }