From 6f0ed4dacaf6c0086471d8ed7cb3a7b4351fdb2a Mon Sep 17 00:00:00 2001 From: Kumar Pallav Date: Wed, 2 Sep 2020 12:41:31 +0530 Subject: [PATCH 1/6] MTM-28374 Implement and document use cases employing the c#-based microservice --- .../DeviceTemperature/DeviceTemperature.sln | 39 ++ .../DeviceTemperature/Dockerfile | 13 + .../DeviceTemperature/build.cake | 130 +++++ .../DeviceTemperature/build.ps1 | 256 ++++++++++ .../DeviceTemperature/deploy.ps1 | 337 +++++++++++++ .../images/multi/cumulocity.json | 13 + .../src/DeviceMicroservice/AlarmText.cs | 12 + .../Controllers/ThermostatController.cs | 110 ++++ .../Controllers/WeatherForecastController.cs | 39 ++ .../DeviceMicroservice.csproj | 13 + .../src/DeviceMicroservice/Program.cs | 40 ++ .../Properties/launchSettings.json | 40 ++ .../src/DeviceMicroservice/Startup.cs | 51 ++ .../src/DeviceMicroservice/Thermometer.cs | 22 + .../src/DeviceMicroservice/WeatherForecast.cs | 15 + .../appsettings.Development.json | 9 + .../src/DeviceMicroservice/appsettings.json | 10 + .../ReadMe.md | 64 +++ .../create.ps1 | 356 +++++++++++++ .../global.json | 5 + .../microservice.ps1 | 470 ++++++++++++++++++ 21 files changed, 2044 insertions(+) create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/DeviceTemperature.sln create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/Dockerfile create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/build.cake create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/build.ps1 create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/deploy.ps1 create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/images/multi/cumulocity.json create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/AlarmText.cs create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/WeatherForecastController.cs create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/DeviceMicroservice.csproj create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Program.cs create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Properties/launchSettings.json create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Startup.cs create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Thermometer.cs create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/WeatherForecast.cs create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/appsettings.Development.json create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/appsettings.json create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/create.ps1 create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/global.json create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/microservice.ps1 diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/DeviceTemperature.sln b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/DeviceTemperature.sln new file mode 100644 index 0000000..16d7e01 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/DeviceTemperature.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DF77DEC0-2438-4D0E-B220-1AA721C29BD3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceMicroservice", "src\DeviceMicroservice\DeviceMicroservice.csproj", "{2037579D-FA21-4732-B5B0-23101B7F4240}" +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 + {2037579D-FA21-4732-B5B0-23101B7F4240}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Debug|x64.ActiveCfg = Debug|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Debug|x64.Build.0 = Debug|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Debug|x86.ActiveCfg = Debug|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Debug|x86.Build.0 = Debug|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Release|Any CPU.Build.0 = Release|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Release|x64.ActiveCfg = Release|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Release|x64.Build.0 = Release|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Release|x86.ActiveCfg = Release|Any CPU + {2037579D-FA21-4732-B5B0-23101B7F4240}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {2037579D-FA21-4732-B5B0-23101B7F4240} = {DF77DEC0-2438-4D0E-B220-1AA721C29BD3} + EndGlobalSection +EndGlobal diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/Dockerfile b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/Dockerfile new file mode 100644 index 0000000..e318cbf --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS runtime +WORKDIR /app +COPY ./publish/Web ./ + +#ENV SERVER_PORT 4700 +#ENV C8Y_BASEURL "http://baseurl" +#ENV C8Y_BOOTSTRAP_TENANT "tenant" +#ENV C8Y_BOOTSTRAP_USERNAME "username" +#ENV C8Y_BOOTSTRAP_PASSWORD "password" +#ENV C8Y_MICROSERIVCE_ISOLATION "PER_TENANT" + +ENTRYPOINT ["dotnet", "DeviceMicroservice.dll"] + diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/build.cake b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/build.cake new file mode 100644 index 0000000..7cbcdcf --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/build.cake @@ -0,0 +1,130 @@ +#addin nuget:https://www.nuget.org/api/v2/?package=cake.docker&version=0.11.0 + + +using Path = System.IO.Path; +using IO = System.IO; + +////////////////////////////////////////////////////////////////////// +// ARGUMENTS +////////////////////////////////////////////////////////////////////// + +var target = Argument("target", "Default"); +var configuration = Argument("configuration", "Release"); + +////////////////////////////////////////////////////////////////////// +// GLOBAL VARIABLES +////////////////////////////////////////////////////////////////////// + +var projectName = "DeviceMicroservice"; +var binaryDir = Directory("./src/"+ projectName +"/bin"); +var objectDir = Directory("./src/"+ projectName +"/obj"); +var projectFile = "./src/"+ projectName +"/"+ projectName +".csproj"; +var publishDir = "./publish"; +var webProject = "./src/"+ projectName +""; +var imageName = ""+ projectName +":latest"; + imageName = imageName.ToLowerInvariant(); + +////////////////////////////////////////////////////////////////////// +// PRIVATE TASKS +////////////////////////////////////////////////////////////////////// + + +Task("Clean") + .Does(() => + { + CleanDirectory(binaryDir); + CleanDirectory(objectDir); + CleanDirectory(publishDir); + + if (FileExists("./images/single/image.tar")){ + DeleteFile("./images/single/image.tar"); + } + if (FileExists("./images/single/image.zip")){ + DeleteFile("./images/single/image.zip"); + } + if (FileExists("./images/multi/image.tar")){ + DeleteFile("./images/multi/image.tar"); + } + if (FileExists("./images/multi/image.zip")){ + DeleteFile("./images/multi/image.zip"); + } + if (FileExists("./image.zip")){ + DeleteFile("./image.zip"); + } + }); + +Task("Build") + .IsDependentOn("Clean") + .Does(()=>{ + + DotNetCoreRestore(projectFile); + DotNetCoreBuild(projectFile, new DotNetCoreBuildSettings + { + Configuration = configuration + }); + //MSBuild(projectFile); + }); + +Task("DotnetPublish") + .IsDependentOn("Clean") + .Does(() => + { + DotNetCorePublish(webProject, new DotNetCorePublishSettings + { + Configuration = configuration, + OutputDirectory = Path.Combine(publishDir, "Web") + }); + }); + +Task("Docker-Build") +.IsDependentOn("DotnetPublish") +.Does(() => { + var buildSettings = new DockerImageBuildSettings { Tag = new[] {imageName}}; + DockerBuild(buildSettings, "./"); + + var saveSettingsMulti = new DockerImageSaveSettings { Output = "./images/multi/image.tar" }; + DockerSave(saveSettingsMulti,new[] {imageName}); + + var filesMulti = new [] { + "./images/multi/image.tar", + "./images/multi/cumulocity.json" }; + + Zip("./images/multi", "image.zip", filesMulti); + CopyFileToDirectory("image.zip", "./images/multi"); + + if (FileExists("./image.zip")) + { + DeleteFile("./image.zip"); + } +}); + +Task("Single-DockerImage") +.Does(() => { + var saveSettingsSingle = new DockerImageSaveSettings { Output = "images/single/image.tar" }; + DockerSave(saveSettingsSingle,new[] {imageName}); + + var filesSingle = new [] { + "./images/single/image.tar", + "./images/single/cumulocity.json" }; + + Zip("./images/single", "image.zip", filesSingle); + CopyFileToDirectory("image.zip", "./images/single"); +}); + +Task("Docker-Run") +.Does(() => { + DockerRun("-p 8999:4700 "+ imageName, "", ""); +}); + +////////////////////////////////////////////////////////////////////// +// TASKS +////////////////////////////////////////////////////////////////////// + +Task("Default") + .IsDependentOn("Docker-Build"); + +////////////////////////////////////////////////////////////////////// +// EXECUTION +////////////////////////////////////////////////////////////////////// + +RunTarget(target); diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/build.ps1 b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/build.ps1 new file mode 100644 index 0000000..a336e29 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/build.ps1 @@ -0,0 +1,256 @@ +########################################################################## +# This is the Cake bootstrapper script for PowerShell. +# This file was downloaded from https://github.com/cake-build/resources +# Feel free to change this file to fit your needs. +########################################################################## + +<# + +.SYNOPSIS +This is a Powershell script to bootstrap a Cake build. + +.DESCRIPTION +This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) +and execute your Cake build script with the parameters you provide. + +.PARAMETER Script +The build script to execute. +.PARAMETER Target +The build script target to run. +.PARAMETER Configuration +The build configuration to use. +.PARAMETER Verbosity +Specifies the amount of information to be displayed. +.PARAMETER ShowDescription +Shows description about tasks. +.PARAMETER DryRun +Performs a dry run. +.PARAMETER SkipToolPackageRestore +Skips restoring of packages. +.PARAMETER ScriptArgs +Remaining arguments are added here. + +.LINK +https://cakebuild.net + +#> + +[CmdletBinding()] +Param( + [string]$Script = "build.cake", + [string]$Target, + [string]$Configuration, + [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] + [string]$Verbosity, + [switch]$ShowDescription, + [Alias("WhatIf", "Noop")] + [switch]$DryRun, + [switch]$SkipToolPackageRestore, + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$ScriptArgs +) + +# Attempt to set highest encryption available for SecurityProtocol. +# PowerShell will not set this by default (until maybe .NET 4.6.x). This +# will typically produce a message for PowerShell v2 (just an info +# message though) +try { + # Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48) + # Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't + # exist in .NET 4.0, even though they are addressable if .NET 4.5+ is + # installed (.NET 4.5 is an in-place upgrade). + # PowerShell Core already has support for TLS 1.2 so we can skip this if running in that. + if (-not $IsCoreCLR) { + [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48 + } + } catch { + Write-Output 'Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to upgrade to .NET Framework 4.5+ and PowerShell v3' + } + +[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null +function MD5HashFile([string] $filePath) +{ + if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) + { + return $null + } + + [System.IO.Stream] $file = $null; + [System.Security.Cryptography.MD5] $md5 = $null; + try + { + $md5 = [System.Security.Cryptography.MD5]::Create() + $file = [System.IO.File]::OpenRead($filePath) + return [System.BitConverter]::ToString($md5.ComputeHash($file)) + } + finally + { + if ($file -ne $null) + { + $file.Dispose() + } + } +} + +function GetProxyEnabledWebClient +{ + $wc = New-Object System.Net.WebClient + $proxy = [System.Net.WebRequest]::GetSystemWebProxy() + $proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials + $wc.Proxy = $proxy + return $wc +} + +Write-Host "Preparing to run build script..." + +if(!$PSScriptRoot){ + $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent +} + +$TOOLS_DIR = Join-Path $PSScriptRoot "tools" +$ADDINS_DIR = Join-Path $TOOLS_DIR "Addins" +$MODULES_DIR = Join-Path $TOOLS_DIR "Modules" +$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" +$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" +$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" +$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" +$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" +$ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" +$MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" + +# Make sure tools folder exists +if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { + Write-Verbose -Message "Creating tools directory..." + New-Item -Path $TOOLS_DIR -Type Directory | Out-Null +} + +# Make sure that packages.config exist. +if (!(Test-Path $PACKAGES_CONFIG)) { + Write-Verbose -Message "Downloading packages.config..." + try { + $wc = GetProxyEnabledWebClient + $wc.DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) + } catch { + Throw "Could not download packages.config." + } +} + +# Try find NuGet.exe in path if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Trying to find nuget.exe in PATH..." + $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } + $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 + if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { + Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." + $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName + } +} + +# Try download NuGet.exe if not exists +if (!(Test-Path $NUGET_EXE)) { + Write-Verbose -Message "Downloading NuGet.exe..." + try { + $wc = GetProxyEnabledWebClient + $wc.DownloadFile($NUGET_URL, $NUGET_EXE) + } catch { + Throw "Could not download NuGet.exe." + } +} + +# Save nuget.exe path to environment to be available to child processed +$env:NUGET_EXE = $NUGET_EXE +$env:NUGET_EXE_INVOCATION = if ($IsLinux -or $IsMacOS) { + "mono `"$NUGET_EXE`"" +} else { + "`"$NUGET_EXE`"" +} + +# Restore tools from NuGet? +if(-Not $SkipToolPackageRestore.IsPresent) { + Push-Location + Set-Location $TOOLS_DIR + + # Check for changes in packages.config and remove installed tools if true. + [string] $md5Hash = MD5HashFile $PACKAGES_CONFIG + if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or + ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { + Write-Verbose -Message "Missing or changed package.config hash..." + Get-ChildItem -Exclude packages.config,nuget.exe,Cake.Bakery | + Remove-Item -Recurse -Force + } + + Write-Verbose -Message "Restoring tools from NuGet..." + + $NuGetOutput = Invoke-Expression "& $env:NUGET_EXE_INVOCATION install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occurred while restoring NuGet tools." + } + else + { + $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" + } + Write-Verbose -Message ($NuGetOutput | Out-String) + + Pop-Location +} + +# Restore addins from NuGet +if (Test-Path $ADDINS_PACKAGES_CONFIG) { + Push-Location + Set-Location $ADDINS_DIR + + Write-Verbose -Message "Restoring addins from NuGet..." + $NuGetOutput = Invoke-Expression "& $env:NUGET_EXE_INVOCATION install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occurred while restoring NuGet addins." + } + + Write-Verbose -Message ($NuGetOutput | Out-String) + + Pop-Location +} + +# Restore modules from NuGet +if (Test-Path $MODULES_PACKAGES_CONFIG) { + Push-Location + Set-Location $MODULES_DIR + + Write-Verbose -Message "Restoring modules from NuGet..." + $NuGetOutput = Invoke-Expression "& $env:NUGET_EXE_INVOCATION install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" + + if ($LASTEXITCODE -ne 0) { + Throw "An error occurred while restoring NuGet modules." + } + + Write-Verbose -Message ($NuGetOutput | Out-String) + + Pop-Location +} + +# Make sure that Cake has been installed. +if (!(Test-Path $CAKE_EXE)) { + Throw "Could not find Cake.exe at $CAKE_EXE" +} + +$CAKE_EXE_INVOCATION = if ($IsLinux -or $IsMacOS) { + "mono `"$CAKE_EXE`"" +} else { + "`"$CAKE_EXE`"" +} + + # Build an array (not a string) of Cake arguments to be joined later +$cakeArguments = @() +if ($Script) { $cakeArguments += "`"$Script`"" } +if ($Target) { $cakeArguments += "-target=`"$Target`"" } +if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } +if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } +if ($ShowDescription) { $cakeArguments += "-showdescription" } +if ($DryRun) { $cakeArguments += "-dryrun" } +$cakeArguments += $ScriptArgs + +# Start Cake +Write-Host "Running build script..." +Invoke-Expression "& $CAKE_EXE_INVOCATION $($cakeArguments -join " ")" +exit $LASTEXITCODE diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/deploy.ps1 b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/deploy.ps1 new file mode 100644 index 0000000..791e5c6 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/deploy.ps1 @@ -0,0 +1,337 @@ +Param( + [alias("s")] + [parameter(Mandatory = $false)] [string]$url, + + [alias("u")] + [parameter(Mandatory = $false)] [string]$username, + + [alias("p")] + [parameter(Mandatory = $false)] [string]$password, + + [alias("an")] + [parameter(Mandatory = $false)] [string]$appname, + + [alias("f")] + [parameter(Mandatory = $false)] [string]$file +) + +function Select-Value { + param + ( + [Parameter(Mandatory=$true, ValueFromPipeline=$true, HelpMessage="Data to process")] + $InputObject + ) + process { + $InputObject.Value + } +} +function Get-IniFile { + param( + [parameter(Mandatory = $true)] [string] $filePath + ) + + $anonymous = "NoSection" + + $ini = @{} + switch -regex -file $filePath { + "^\[(.+)\]$" { + $section = $matches[1] + $ini[$section] = @{} + $CommentCount = 0 + } + + "^(;.*)$" { + if (!($section)) { + $section = $anonymous + $ini[$section] = @{} + } + $value = $matches[1] + $CommentCount = $CommentCount + 1 + $name = "Comment" + $CommentCount + $ini[$section][$name] = $value + } + + "(.+?)\s*=\s*(.*)" { + if (!($section)) { + $section = $anonymous + $ini[$section] = @{} + } + $name,$value = $matches[1..2] + $ini[$section][$name] = $value + } + } + + return $ini +} + +function getResponseAppNameJson([Parameter(Mandatory=$true)]$username,[Parameter(Mandatory=$true)]$pass,[Parameter(Mandatory=$true)]$site,[Parameter(Mandatory=$true)]$appname) { + $aid = 0 + $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$pass))) + $requestAppName = "https://$site/application/applicationsByName/$appname" + $responseAppNameJson =Invoke-WebRequest -Uri $requestAppName -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -ErrorAction SilentlyContinue | ConvertFrom-Json + + if($responseAppNameJson) { + $app = (($responseAppNameJson).applications | Where-Object {$_.name -eq $appname}) + if($app) { + $aid = $app.id + } + } + if($aid-eq 0){ + throw "Application does not exist." + } + return $aid +} +function Invoke-DataUpdate { + [CmdletBinding()] + param + ( + [Parameter(Mandatory=$false, Position=0)] + [System.Int32] + $appid = 0, + + [Parameter(Mandatory=$false, Position=1)] + [System.String] + $responseJson = "" + ) + + $id = getResponseAppNameJson $username $password $url $appname + + if($id -eq 0){ + throw "Application does not exist." + } + # + $uri = "https://$($url)/application/applications/$($id)/binaries" + $filePath = Resolve-Path -Path ".\images\multi\image.zip" + $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password))) + + Invoke-MultipartFormDataUpload -InFile $filePath -Uri $uri -Header $base64AuthInfo + + Write-Host "I'm done!" +} + +function Invoke-MultipartFormDataUpload { + PARAM + ( + [string][parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$InFile, + [string]$ContentType, + [Uri][parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$Uri, + [string] [parameter(Mandatory = $true)] $Header + ) + BEGIN { + if (-not (Test-Path $InFile)) { + $errorMessage = ("File {0} missing or unable to read." -f $InFile) + $exception = New-Object System.Exception $errorMessage + $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, 'MultipartFormDataUpload', ([Management.Automation.ErrorCategory]::InvalidArgument), $InFile + $PSCmdlet.ThrowTerminatingError($errorRecord) + } + + if (-not $ContentType) { + Add-Type -AssemblyName System.Web + + $mimeType = [Web.MimeMapping]::GetMimeMapping($InFile) + + if ($mimeType) { + $ContentType = $mimeType + } + else { + $ContentType = "application/octet-stream" + } + } + } + PROCESS { + $fileName = Split-Path $InFile -leaf + $boundary = [guid]::NewGuid().ToString() + + $fileBin = [IO.File]::ReadAllBytes($InFile) + $enc = [Text.Encoding]::GetEncoding("iso-8859-1") + + $template = @' +--{0} +Content-Disposition: form-data; name="fileData"; filename="{1}" +Content-Type: {2} + +{3} +--{0}-- + +'@ + + $body = $template -f $boundary, $fileName, $ContentType, $enc.GetString($fileBin) + Write-Host "Header:" $Header + + try { + return Invoke-WebRequest -Uri $Uri ` + -Method Post ` + -ContentType "multipart/form-data; boundary=$boundary" ` + -Body $body ` + -Headers @{Authorization=("Basic {0}" -f $Header)} + } + catch { + $PSCmdlet.ThrowTerminatingError($_) + } + } + END { } +} + +function Write-ErrorMsg($exp) { + Write-Host "StatusCode:" $exp.Message + #Write-Host "Response:" $exp.Response + #$result = $exp.Response.GetResponseStream() + #$reader = New-Object System.IO.StreamReader($result) + #$reader.BaseStream.Position = 0 + #$reader.DiscardBufferedData() + #$responseBody = $reader.ReadToEnd() + #Write-Host "ResponseBody:" $responseBody +} + +if (!$file -and !$url -and !$username -and !$password -and !$appname) { + #1. Just call deploy.ps1 + #a. The script looks for a "settings.ini" in the same directory. If found, uses the credentials and tenant URL from that file + #b. If settings.ini is not found, an error is shown + + + $file = "settings.ini" + $isLocalFile = $true + + if (Test-Path $file) { + $isLocalFile = $true; + } +else{ + + $isLocalFile = $false + $appdata = Get-Childitem env:APPDATA | Select-Value + $file = "$appdata\c8y\$file" + + if (-not(Test-Path $file)) { + throw [IO.FileNotFoundException] "$file not found." + } + } + + if(-not($isLocalFile)) { + $settingsIni = Get-IniFile $file + } +else{ + $settingsIni = Get-IniFile .\$file + } + + $username = $settingsIni.deploy.username + $password = $settingsIni.deploy.password + $url = $settingsIni.deploy.url + $appname = $settingsIni.deploy.appname + + Write-Host $username + Write-Host $url + Write-Host $appname + + try { + $appid = 0 + $responseJson = "" + Invoke-DataUpdate $appid $responseJson + } + catch { + Write-ErrorMsg $_.Exception + } +} +ElseIf($file -and !$url -and !$username -and !$password -and !$appname) { + + $isLocalFile = $true + + if (Test-Path $file) { + $isLocalFile = $true + } + else{ + + $isLocalFile = $false + $appdata = Get-Childitem env:APPDATA | Select-Value + $file = "$appdata\c8y\$file" + + if (-not(Test-Path $file)) { + throw [IO.FileNotFoundException] "$file not found." + } + } + + if(-not($isLocalFile)) { + $settingsIni = Get-IniFile $file + } +else{ + $settingsIni = Get-IniFile .\$file + } + + $settingsIni = Get-IniFile .\$file + + $username = $settingsIni.deploy.username + $password = $settingsIni.deploy.password + $url = $settingsIni.deploy.url + $appname = $settingsIni.deploy.appname + + Write-Host $username + Write-Host $url + Write-Host $appid + + try { + + $appid = 0 + $responseJson = "" + Invoke-DataUpdate $appid $responseJson + } + catch { + Write-ErrorMsg $_.Exception + } +} +ElseIf($file -and ($url -or $username -or $password -or $appname)) { + + if (Test-Path $file) { + $isLocalFile = $true + } +else{ + + $isLocalFile = $false + $appdata = Get-Childitem env:APPDATA | Select-Value + $file = "$appdata\c8y\$file" + + if (-not(Test-Path $file)) { + throw [IO.FileNotFoundException] "$file not found." + } + } + + if(-not($isLocalFile)) { + $settingsIni = Get-IniFile $file + } +else{ + $settingsIni = Get-IniFile .\$file + } + + + $settingsIni = Get-IniFile .\$file + + if(!$url){ + $url = $settingsIni.deploy.url + } + if(!$username){ + $username = $settingsIni.deploy.username + } + if(!$password){ + $password = $settingsIni.deploy.password + } + if(!$appname){ + $appname = $settingsIni.deploy.appname + } + + try { + + $appid = 0 + $responseJson = "" + Invoke-DataUpdate $appid $responseJson + + } + catch [System.Management.Automation.RuntimeException] { + # get error record + [Management.Automation.ErrorRecord]$e = $_ + Write-ErrorMsg $e.Exception + } +} +Else { + $File_Path_Error = [string]"The file path is not valid" + Write-Error $File_Path_Error +} + + diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/images/multi/cumulocity.json b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/images/multi/cumulocity.json new file mode 100644 index 0000000..1a8b4f0 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/images/multi/cumulocity.json @@ -0,0 +1,13 @@ +{ + "apiVersion": "1", + "version": "1.1.2" + "provider": { + "name": "Cumulocity GmbH" + }, + "contextPath": "helloworld", + "isolation": "PER_TENANT", + "resources": { + "cpu": "2000m", + "memory": "2Gi" + } +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/AlarmText.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/AlarmText.cs new file mode 100644 index 0000000..66f3202 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/AlarmText.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace DeviceMicroservice +{ + public class AlarmText + { + public string alarmText { get; set; } + } +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs new file mode 100644 index 0000000..3ffe35a --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Cumulocity.SDK.Microservices.Settings; +using Newtonsoft.Json; +using RestSharp; +using DeviceMicroservice; +using DeviceMicroservice.Controllers; +using Microsoft.Extensions.Logging; + +namespace ThermostatMicroservice.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ThermostatController : ControllerBase + { + public Platform PlatformSettings { get; } + private readonly ILogger _logger; + + public ThermostatController(Platform platform, ILogger logger) + { + PlatformSettings = platform; + _logger = logger; + } + + // This endpoint is to retrive information about all the thermostat thermometers. + // GET api/Thermostat/thermometers + [HttpGet("thermometers")] + public async Task> Get() + { + var client = new RestClient(PlatformSettings.BASEURL + "/inventory/managedObjects?fragmentType=c8y_IsThermometer&fragmentType=c8y_SupportedMeasurements)"); + var request = new RestRequest(Method.GET); + request.AddHeader("Authorization", Request.Headers["Authorization"]); + request.AddHeader("Content-Type", "application/json"); + request.AddHeader("Accept", "application/json"); + IRestResponse response = client.Execute(request); + Console.WriteLine("The thermometers in the inventory are returned."); + return response.Content; + } + + // This endpoint creates thermometer (temprature measurment supported device in cumulocity) as per the JSON body of the object and sends one temprature measurment which is there in the body. + // POST api/Thermostat/thermometers + [HttpPost("thermometers")] + public async Task> Post([FromBody] Thermometer thermometer) + { + try + { + if (thermometer == null) + return "Add temprature object json in the request body."; + + var client = new RestClient(PlatformSettings.BASEURL+"/inventory/managedObjects"); + var request = new RestRequest(Method.POST); + request.AddHeader("Authorization", Request.Headers["Authorization"]); + request.AddHeader("Content-Type", "application/json"); + request.AddHeader("Accept", "application/json"); + request.AddParameter("application/json", "{\"name\": \"" + thermometer.nameID + "\", \"c8y_IsDevice\": {}, \"c8y_IsThermometer\": {},\"c8y_SupportedMeasurements\": [\"c8y_TemperatureMeasurement\"]}", ParameterType.RequestBody); + IRestResponse response = client.Execute(request); + Console.WriteLine((response.StatusCode.ToString().Equals("Created")) ? "Thermometer created !!" : "Thermometer couldn't be created !! \n Below is the response body from cumulocity \n"); + Console.WriteLine(response.Content); + return thermometer.ToString(); + } + catch(Exception ex) + { + return ex.ToString(); + } + } + + // We are calling the default weather forcast endpoint and if any returned forecast temprature is greater than 25º then we are raising an alarm with alarmText + // POST thermometers//temperatures + [HttpPost("/thermometers/{id}/temperatures")] + public string Post([FromBody]AlarmText alarmText) + { + try + { + //Mock of hitting endpoint /weatherforecast to take the latest entry and post the temperatureC value to Cumulocity IoT under the specified device(by id). + WeatherForecastController w = new WeatherForecastController(_logger); + var forecastList = w.Get(); + var id = Request.RouteValues.Values.ElementAt(0); // To retrive the device ID from the Request URL , which will be later used to create an alarm. + + // If any of the forecast is higher than 25º then we raise an alarm for the device ID passed in the request. + foreach (var forecast in forecastList) + { + if (forecast.TemperatureC > 25) + { + Console.Write(forecast.TemperatureC + "º temprature was recorded and this is considerded as " + forecast.Summary + " temprature. \nWe are raising an alarm on object with ID "+ id + " in the platform."); + var client = new RestClient(PlatformSettings.BASEURL+"/alarm/alarms"); + var request = new RestRequest(Method.POST); + request.AddHeader("Authorization", Request.Headers["Authorization"]); + request.AddHeader("Content-Type", "application/vnd.com.nsn.cumulocity.alarm+json"); + request.AddHeader("Accept", "application/vnd.com.nsn.cumulocity.alarm+json"); + request.AddParameter("application/vnd.com.nsn.cumulocity.alarm+json", "{\"source\": {\"id\": \"" + id + "\" },\"type\":\"c8y_hightemperature_alarm\",\"text\":\"" + alarmText.alarmText + "\",\"severity\":\"WARNING\",\"status\":\"ACTIVE\",\"time\":\"2020-03-03T12:03:27.845Z\"}", ParameterType.RequestBody); + IRestResponse response = client.Execute(request); + Console.WriteLine(response.Content); + return response.Content.ToString(); + } + } + + } + catch (Exception e) + { + return e.ToString(); + } + + return "No High temprature was observed in the forecast. So no alarms were raised. Please hit this endpoint till high temprature is observed."; + } + + } +} \ No newline at end of file diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/WeatherForecastController.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..02f1648 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/WeatherForecastController.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace DeviceMicroservice.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/DeviceMicroservice.csproj b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/DeviceMicroservice.csproj new file mode 100644 index 0000000..b594129 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/DeviceMicroservice.csproj @@ -0,0 +1,13 @@ + + + netcoreapp3.1 + $(RestoreSources);../nugets;https://api.nuget.org/v3/index.json + false + + + + + + + + \ No newline at end of file diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Program.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Program.cs new file mode 100644 index 0000000..e175154 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Program.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace DeviceMicroservice +{ + + +using System.Net; +using Cumulocity.SDK.Microservices.Configure; +using Microsoft.AspNetCore; + + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseKestrel(options => + { + var port = Environment.GetEnvironmentVariable("SERVER_PORT"); + options.Listen(IPAddress.Parse("0.0.0.0"), Int32.TryParse(port, out var portNumber) ? portNumber : 8080); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole().SetMinimumLevel(LogLevel.Information); + }) + .UseStartup() + .Build(); + } +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Properties/launchSettings.json b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Properties/launchSettings.json new file mode 100644 index 0000000..888ea63 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Properties/launchSettings.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:38049", + "sslPort": 44370 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "DeviceMicroservice": { + "commandName": "Project", + "launchBrowser": true, + // "launchUrl": "weatherforecast", + "launchUrl": "api/device", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "SERVER_PORT": "47000", + "C8Y_MICROSERIVCE_ISOLATION": "PER_TENANT", + "C8Y_BASEURL": "", + "C8Y_BASEURL_MQTT": "", + "C8Y_TENANT": "", + "C8Y_PASSWORD": "", + "C8Y_USERNAME": "", + "C8Y_BOOTSTRAP_TENANT": "", + "C8Y_BOOTSTRAP_USER": "", + "C8Y_BOOTSTRAP_PASSWORD": "" + } + } + } +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Startup.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Startup.cs new file mode 100644 index 0000000..c929b16 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Startup.cs @@ -0,0 +1,51 @@ + +namespace DeviceMicroservice +{ + + + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using Newtonsoft.Json.Serialization; + using Cumulocity.SDK.Microservices.BasicAuthentication; + using Cumulocity.SDK.Microservices.Configure; + using Cumulocity.SDK.Microservices.Services; + using Cumulocity.SDK.Microservices.Settings; + using Cumulocity.SDK.Microservices.Utils; + using Microsoft.AspNetCore.Builder; + + 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. + public void ConfigureServices(IServiceCollection services) + { + services.AddMemoryCache(); + services.AddCumulocityAuthentication(Configuration); + services.AddPlatform(Configuration); + services.AddSingleton(); + services.AddSingleton(); + + //MVC + services.AddControllers(options => options.EnableEndpointRouting = false); + services.Replace(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(TimedLogger<>))); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + app.UseAuthentication(); + app.UseBasicAuthentication(); + app.UseMvcWithDefaultRoute(); + } + } +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Thermometer.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Thermometer.cs new file mode 100644 index 0000000..693e2c6 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Thermometer.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace DeviceMicroservice +{ + public class Thermometer + { + public string nameID { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public override string ToString() + { + return "Thermometer Name : " + nameID + "\nTemperatureC: " + TemperatureC + "\nTemperatureF: " + TemperatureF; + } + + } +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/WeatherForecast.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/WeatherForecast.cs new file mode 100644 index 0000000..154142c --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace DeviceMicroservice +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/appsettings.Development.json b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/appsettings.json b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/appsettings.json new file mode 100644 index 0000000..d9d9a9b --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md new file mode 100644 index 0000000..0fe803e --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md @@ -0,0 +1,64 @@ +### Overview + +This example provides a step-by-step guide to develop a simple microservice in Cumulocity. It uses Cake (C# Make), which is a cross-platform build automation system. + +To start building .NET apps, you just need to download and install the [.NET SDK](https://www.microsoft.com/net/download). Follow the instructions on the download page for the last stable release or alternatively you can also try using 3.1. + +If you use Linux, visit the [MonoDevelop website](http://www.monodevelop.com/) for download packages and more details about our cross-platform IDE. Follow the instructions on the download page for the last stable release or alternatively you can also try using 6.8 or higher version of mono [IDE](http://www.mono-project.com/download/#download-lin). Note, that Mono-devel is required to compile code. + +The initial script was used to create a demo, which makes it easier to create an example microservice project with dependency management and then deploy it on the server. The script attempts to download the package from the sources listed in the project file, and next a reference is added to the appropriate project file. In addition, the script creates the appropriate Dockerfile to take into account the naming of projects. Next it will create a Docker image based on a Dockerfile. + +The application created in this way uses the ASP.NET Web API framework to create a web API. The API runs on an isolated web server called Kestrel and as a foreground job, which makes it work really well with Docker. + + +### Building and deploying Hello World on Windows + +Refer to the [online documentation|https://cumulocity.com/guides/microservice-sdk/cs/] and follow the step-by-step tutorial to develop a simple microservice in Cumulocity IoT. The online documentation also contains more details about building microservices with the SDK for C#. + +### Extending hello-world microservice to create a smart thermometer microservice + +This microservice in this folder is an extension of the hello world microservice mentioned in [online documentation|https://cumulocity.com/guides/microservice-sdk/cs/] . +This scenario has smart thermometers that send readings to Cumulocity IoT. This example has an extra Controller class called `ThermostatController` which has 3 new endpoints with the following Implementation - + +- Endpoint - 1 +Implement the endpoint //thermometers +Action: Register a new thermometer +Method: POST +Payload: + { + "nameID" : "", + "TemperatureC" : int + } + +The endpoint internally calls /inventory/managedObjects POST request with the below mentioned payload to create a thermometer in the platform - +{ + "name": "" + "c8y_IsDevice": {}, + "c8y_IsThermometer": {}, + "c8y_SupportedMeasurements": [ + "c8y_TemperatureMeasurement" + ] +} + +The custom fragment `c8y_IsThermometer` is used to identify the devices as thermometer. + +- Endpoint - 2 +Implement the endpoint //thermometers +Action: Retrieve all registered thermometers +Method: GET +internally the microservice calls /inventory/managedObjects?fragmentType=c8y_IsThermometer&fragmentType=c8y_SupportedMeasurements + +- Endpoint - 3 +Implement the endpoint /thermometers/{id}/temperatures/ +Method: POST +Payload: +{ + "alarmText" : +} + +Action: When invoked with a POST request, internally the microservice will invoke the already implemented (Hello world) endpoint /weatherforecast to take the latest entry of weather forecasts and if any forecast is greater than 25º then post the temperatureC value to Cumulocity IoT under the specified device (by id). Below are the specification of the alarm to be raised - +Severity: WARNING +Type: c8y_hightemperature_alarm +Text: As fetched from the payload body. + + diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/create.ps1 b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/create.ps1 new file mode 100644 index 0000000..278f767 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/create.ps1 @@ -0,0 +1,356 @@ +Param( +[string]$ProjectName = "Project", +[string]$WebApiProject = "WebApi" +) + +If( ($ProjectName -And !$WebApiProject) -Or (!$ProjectName -And $WebApiProject) ) +{ + Write-Host "Break Out! The script needs two not null parameters." + Break +} + +If( (!$ProjectName -And !$WebApiProject) -Or ($ProjectName -eq "Project" -And $WebApiProject -eq "WebApi") ) +{ + $ProjectName = Read-Host -Prompt 'Enter the solution name:' + $WebApiProject = Read-Host -Prompt 'Enter the name of a web API project:' +} + +If(!$ProjectName -And !$WebApiProject) +{ + $ProjectName = Read-Host -Prompt 'Enter the solution name:' + $WebApiProject = Read-Host -Prompt 'Enter the name of a web API project:' +} + + +IF ([string]::IsNullOrWhitespace($ProjectName )){ + Write-Host "The solution name is empty."; + exit; +} +IF ([string]::IsNullOrWhitespace($WebApiProject)){ + Write-Host "The name of web api project is empty." + exit; +} +##################### +#######CREATE######## +##################### +function Select-Value { + param + ( + [Parameter(Mandatory=$true, ValueFromPipeline=$true, HelpMessage="Data to process")] + $InputObject + ) + process { + $InputObject.Value + } +} +function Get-IniFile { + param( + [parameter(Mandatory = $true)] [string] $filePath + ) + + $anonymous = "NoSection" + + $ini = @{} + switch -regex -file $filePath { + "^\[(.+)\]$" { + $section = $matches[1] + $ini[$section] = @{} + $CommentCount = 0 + } + + "^(;.*)$" { + if (!($section)) { + $section = $anonymous + $ini[$section] = @{} + } + $value = $matches[1] + $CommentCount = $CommentCount + 1 + $name = "Comment" + $CommentCount + $ini[$section][$name] = $value + } + + "(.+?)\s*=\s*(.*)" { + if (!($section)) { + $section = $anonymous + $ini[$section] = @{} + } + $name,$value = $matches[1..2] + $ini[$section][$name] = $value + } + } + + return $ini +} + +$file = "settings.ini" +$isLocalFile = $true +$settingsFile = (Get-Childitem -Include *settings.ini* -File -Recurse ) | % { $_.FullName } + +if (($settingsFile) -and (Test-Path $settingsFile)) { + $isLocalFile = $true; +} +else{ + + $isLocalFile = $false + $appdata = Get-Childitem env:APPDATA | Select-Value + $file = "$appdata\c8y\$file" + +} +if ( (-not(Test-Path $file)) -and (-not(Test-Path .\$file))){ + #throw [IO.FileNotFoundException] "$file not found." +}else{ + + if(-not($isLocalFile)) { + $settingsIni = Get-IniFile $file + } + else + { + $settingsIni = Get-IniFile .\$file + } + + $username = $settingsIni.deploy.username + $password = $settingsIni.deploy.password + $url = $settingsIni.deploy.url + $appname = $settingsIni.deploy.appname + $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password))) + + Write-Host "https://$url/application/applications" + + $Result = Invoke-RestMethod -Uri "https://$url/application/applicationsByName/$appname" ` + -Headers @{Authorization = ("Basic {0}" -f $base64AuthInfo)} ` + -ErrorVariable RestError -ErrorAction "SilentlyContinue" + if ($RestError) { + $HttpStatusCode = $RestError.ErrorRecord.Exception.Response.StatusCode.value__ + $HttpStatusDescription = $RestError.ErrorRecord.Exception.Response.StatusDescription + + Write-Output "[ERROR] Error while connecting to platform" + Write-Output "Http Status Code: $($HttpStatusCode) `nHttp Status Description: $($HttpStatusDescription)" + Exit + } + else { + + if( -not($Result.applications.id)) + { + $json = '{ + "name": "' + (echo $appname) + '", + "type": "MICROSERVICE", + "key": "' + (echo $appname) + '-key" + }' + + $headers = @{ + Authorization=("Basic {0}" -f $base64AuthInfo) + Accept ="application/json" + } + Invoke-WebRequest -Uri "https://$url/application/applications" -Headers $headers -Method Post -Body $json -ContentType "application/json" + } + } +} +########################### +######Main Project######### +########################### + +mkdir "$ProjectName" +$currentDir = Get-Location +$CakeBuildOutput = "$currentDir/build.cake" +$DockerfileOutput = "$currentDir/Dockerfile" +$DeployfileOutput = "$currentDir/deploy.ps1" + +Move-Item "$CakeBuildOutput" "$currentDir/$projectName/build.cake" +Move-Item "$DockerfileOutput" "$currentDir/$projectName/Dockerfile" +Move-Item "$DeployfileOutput" "$currentDir/$projectName/deploy.ps1" + +(Get-Content "$currentDir\$projectName\build.cake").replace('[ProjectName]', "$WebApiProject") | Set-Content "$currentDir\$projectName\build.cake" +(Get-Content "$currentDir\$projectName\Dockerfile").replace('DockerApp.dll', "$WebApiProject.dll") | Set-Content "$currentDir\$projectName\Dockerfile" + +cd "$ProjectName" +$currentDir = Get-Location + +Invoke-WebRequest https://cakebuild.net/download/bootstrapper/windows -OutFile build.ps1 + +dotnet new sln --name "$ProjectName" + + +mkdir tools +cd tools +$packagescakedir='packages.config' +New-Item "$packagescakedir" -type file +$cakePackages=' + + +' +Add-Content $packagescakedir $cakePackages +cd .. +##################### +######Source######### +##################### +mkdir publish +mkdir -p images/single +mkdir -p images/multi +mkdir src + +$CumulocityJson = '{ + "apiVersion": "1", + "version": "1.0.0", + "provider": { + "name": "Cumulocity GmbH" + }, + "contextPath": "hello", + "isolation": "MULTI_TENANT", + "resources": { + "cpu": "2000m", + "memory": "2Gi" + } +}' +New-Item images\multi\cumulocity.json -type file +Add-Content images\multi\cumulocity.json $CumulocityJson + +cd src +##################### +######WebApi######### +##################### +dotnet new webapi --name "$WebApiProject" --output "$WebApiProject" +$currentDir = Get-Location +$filePath = "$currentDir/$WebApiProject/$WebApiProject.csproj" + +[Xml]$xdoc = Get-Content -Path $filePath -Raw +#RestoreSources +$newNode = $xdoc.CreateElement("RestoreSources") +$newNode.InnerText = "`$(RestoreSources);../nugets;https://api.nuget.org/v3/index.json" +$xdoc.SelectSingleNode("//PropertyGroup[1]").appendChild($newNode) +#PublishWithAspNetCoreTargetManifest +$newNode = $xdoc.CreateElement("PublishWithAspNetCoreTargetManifest") +$newNode.InnerText = "false" +$xdoc.SelectSingleNode("//PropertyGroup[1]").appendChild($newNode) + +$xdoc.Save($filePath) +##################### +######Nugets######### +##################### +mkdir nugets +cd nugets +$currentDir = Get-Location +$start_time = Get-Date + +##FTP + $target = "$currentDir/" + +Invoke-WebRequest http://resources.cumulocity.com/cssdk/releases/Cumulocity.AspNetCore.Authentication.Basic.1006.6.0.nupkg -OutFile Cumulocity.AspNetCore.Authentication.Basic.1006.6.0.nupkg +Invoke-WebRequest http://resources.cumulocity.com/cssdk/releases/Cumulocity.SDK.Microservices.1006.6.0.nupkg -OutFile Cumulocity.SDK.Microservices.1006.6.0.nupkg + +$nugetsFiles = Get-ChildItem $currentDir -Filter *.nupkg + +cd.. +cd $WebApiProject + +$currentDir = Get-Location + +foreach ($file in $nugetsFiles ) +{ + $package = $file.Name + if ($package -like '*Authentication.Basic*') { + $package =($package.Split(".",5) | Select -Index 0,1,2,3) -join "." + } + elseif ($package -like '*Cumulocity.SDK.Microservices*'){ + $package =($package.Split(".",4) | Select -Index 0,1,2) -join "." + } + Write-Host $package + dotnet add package "$package" +} + +$csStartupFile ="Startup.cs" + +$csStartup=" + + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using Newtonsoft.Json.Serialization; + using Cumulocity.SDK.Microservices.BasicAuthentication; + using Cumulocity.SDK.Microservices.Configure; + using Cumulocity.SDK.Microservices.Services; + using Cumulocity.SDK.Microservices.Settings; + using Cumulocity.SDK.Microservices.Utils; + using Microsoft.AspNetCore.Builder; + + 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. + public void ConfigureServices(IServiceCollection services) + { + services.AddMemoryCache(); + services.AddCumulocityAuthentication(Configuration); + services.AddPlatform(Configuration); + services.AddSingleton(); + services.AddSingleton(); + + //MVC + services.AddControllers(options => options.EnableEndpointRouting = false); + services.Replace(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(TimedLogger<>))); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + app.UseAuthentication(); + app.UseBasicAuthentication(); + app.UseMvcWithDefaultRoute(); + } + } +}" + +$varStartup = (get-content -Path $csStartupFile) | select-string -Pattern 'namespace. *' | Select-Object -ExpandProperty LineNumber +$toLineNo = $varStartup[0] - 2; +(Get-Content $csStartupFile | Select-Object -Skip $toLineNo) | Set-Content $csStartupFile + +$varStartup = (get-content $csStartupFile) | select-string -Pattern '{.*' | Select-Object -ExpandProperty LineNumber +(get-content $csStartupFile) | select -First $varStartup[0] | Set-Content $csStartupFile +Add-Content -Path $csStartupFile -Value $csStartup + +$csProgram=" + +using System.Net; +using Cumulocity.SDK.Microservices.Configure; +using Microsoft.AspNetCore; + + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseKestrel(options => + { + var port = Environment.GetEnvironmentVariable(""SERVER_PORT""); + options.Listen(IPAddress.Parse(""0.0.0.0""), Int32.TryParse(port, out var portNumber) ? portNumber : 8080); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection(""Logging"")); + logging.AddConsole().SetMinimumLevel(LogLevel.Information); + }) + .UseStartup() + .Build(); + } +}" + +$varOther = (get-content "Program.cs") | select-string -Pattern '{.*' | Select-Object -ExpandProperty LineNumber +(get-content "Program.cs") | select -First $varOther[0] | Set-Content "Program.cs" +Add-Content -Path "Program.cs" -Value $csProgram + +cd ../.. +dotnet sln add "./src/$WebApiProject/$WebApiProject.csproj" + +Pause diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/global.json b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/global.json new file mode 100644 index 0000000..cc4d115 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "3.1.200" + } +} \ No newline at end of file diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/microservice.ps1 b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/microservice.ps1 new file mode 100644 index 0000000..1e3e429 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/microservice.ps1 @@ -0,0 +1,470 @@ +<# +.SYNOPSIS + Script to pack, deploy and subscribe the application in a single line. + +.DESCRIPTION + Cumulocity provides you with an utility tool for easy microservice packaging, deployment and subscription. The script requires running docker. + https://docs.adamos.com/guides/reference/microservice-package/ + +.PARAMETER mode + pack deploy subscribe -n hello-world -d {url} -u {username} -p {password} -te {tenant} + +.EXAMPLE + microservice.ps1 pack deploy subscribe -n hello-world -d {url} -u {username} -p {password} -te {tenant} + microservice.ps1 subscribe -n hello-world -d {url} -u {username} -p {password} -te {tenant} -id {applicationId} + microservice.ps1 deploy -n hello-world -d {url} -u {username} -p {password} -te {tenant} +#> + +$global:WORK_DIR = $(pwd) +$global:IMAGE_NAME = "" +$global:TAG_NAME = "latest" +$global:DEPLOY_ADDRESS = "" +$global:DEPLOY_TENANT = "" +$global:DEPLOY_USER = "" +$global:DEPLOY_PASSWORD = "" +$global:APPLICATION_NAME = "" +$global:APPLICATION_ID = "" +$global:ZIP_NAME = "" + +$global:PACK = 1 +$global:DEPLOY = 1 +$global:SUBSCRIBE = 1 +$global:HELP = 1 + + +function execute { + [CmdletBinding()] + Param( + [Object[]]$args + ) + + + readInput $args + + cd $WORK_DIR + if ("$HELP".equals("0")) { + printHelp; + exit; + } + + if ( "$PACK".equals("1") -and "$DEPLOY".equals("1") -and "$SUBSCRIBE".equals("1")) { + Write-Output "[INFO] No goal set. Please set pack, deploy or subscribe" + } + + if ("$PACK".equals("0")) { + Write-Output "[INFO] Start packaging" + verifyPackPrerequisits + clearTarget + buildImage + exportImage + zipFile + Write-Output "[INFO] End packaging" + } + if ( "$DEPLOY".Equals("0")) { + Write-Output "[INFO] Start deployment" + deploy + Write-Output "[INFO] End deployment" + } + if ("$SUBSCRIBE".Equals("0")) { + Write-Output "[INFO] Start subsciption" + subscribe + Write-Output "[INFO] End subsciption" + } + exit; +} + + +function readInput { + [CmdletBinding()] + Param( + [Object[]]$args + ) + + Write-Output "[INFO] Read input" + + For ($i = 0; $i -le $args.Count; $i++) { + if ($args[$i] -eq "pack") { + $global:PACK = 0; + } + if ($args[$i] -eq "deploy") { + $global:DEPLOY = 0; + } + if ($args[$i] -eq "subscribe") { + $global:SUBSCRIBE = 0; + } + if (($args[$i] -eq "help") -or ($args[$i] -eq "--help")) { + $global:HELP = 0; + } + if (($args[$i] -eq "-dir") -or ($args[$i] -eq "--directory")) { + $global:WORK_DIR = $args[$i + 1] + $i = $i + 1 + } + if (($args[$i] -eq "-n") -or ($args[$i] -eq "--name")) { + $global:IMAGE_NAME = $args[$i + 1] + $i = $i + 1 + } + if (($args[$i] -eq "-t") -or ($args[$i] -eq "--tag")) { + $global:TAG_NAME = $args[$i + 1] + $i = $i + 1 + } + if (($args[$i] -eq "-d") -or ($args[$i] -eq "--deploy")) { + $global:DEPLOY_ADDRESS = $args[$i + 1] + $i = $i + 1 + } + if (($args[$i] -eq "-u") -or ($args[$i] -eq "--user")) { + $global:DEPLOY_USER = $args[$i + 1] + $i = $i + 1 + } + if (($args[$i] -eq "-p") -or ($args[$i] -eq "--password")) { + $global:DEPLOY_PASSWORD = $args[$i + 1] + $i = $i + 1 + } + if (($args[$i] -eq "-te") -or ($args[$i] -eq "--tenant")) { + $global:DEPLOY_TENANT = $args[$i + 1] + $i = $i + 1 + } + if (($args[$i] -eq "-a") -or ($args[$i] -eq "--application")) { + $global:APPLICATION_NAME = $args[$i + 1] + $i = $i + 1 + } + if (($args[$i] -eq "-id") -or ($args[$i] -eq "--applicationId")) { + $global:APPLICATION_ID = $args[$i + 1] + $i = $i + 1 + } + } + + setDefaults +} + +function setDefaults() { + $global:ZIP_NAME = "$global:IMAGE_NAME.zip" + + if ( "x$APPLICATION_NAME".Equals("x")) { + $global:APPLICATION_NAME = $global:IMAGE_NAME + } +} + +function printHelp() { + + Write-Output "Make sure to first create the application following the step 1 of Running the microservice locally " + Write-Output "Following functions are available. You can run specify them in single execution:" + Write-Output " pack - prepares deployable zip file. Requires following stucture:" + Write-Output " /docker/Dockerfile" + Write-Output " /docker/* - all files within the directory will be included in the docker build. You can copy paste the publish folder here" + Write-Output " /cumulocity.json " + Write-Output " deploy - deploys applicaiton to specified address" + Write-Output " subscribe - subscribes tenant to specified microservice application" + Write-Output " help | --help - prints help" + + Write-Output "Following options are available:" + Write-Output " -dir | --directory # Working directory. Default value'$(pwd)' " + Write-Output " -n | --name # Docker image name" + Write-Output " -t | --tag # Docker tag. Default value 'latest'" + Write-Output " -d | --deploy # Address of the platform the microservice will be uploaded to" + Write-Output " -u | --user # Username used for authentication to the platform" + Write-Output " -p | --password # Password used for authentication to the platform" + Write-Output " -te | --tenant # Tenant used" + Write-Output " -a | --application # Name upon which the application will be registered on the platform. Default value from --name parameter" + Write-Output " -id | --applicationId # Applicaiton used for subscription purposes. Required only for solemn subscribe execution" +} + +function verifyPackPrerequisits() { + Write-Output "[INFO] Check input" + + Set-Variable -Name result -Option AllScope + $result = 0 + verifyParamSet "$IMAGE_NAME" "name" + + isPresent @($((Get-ChildItem -Filter docker -Directory -Recurse -Depth 1 | measure-object -line).Lines), "[ERROR] Stopped: missing docker directory in work directory: $WORK_DIR") + isPresent @($((Get-ChildItem -Filter Dockerfile -File -Recurse -Depth 1 | measure-object -line).Lines), "[ERROR] Stopped: missing dockerfile in work directory: $WORK_DIR") + isPresent @($((Get-ChildItem -Filter cumulocity.json -File -Recurse -Depth 1 | measure-object -line).Lines), "[ERROR] Stopped: missing cumulocity.json in work directory: $WORK_DIR") + + if ( "$result".Equals("1")) { + Write-Output "[WARNING] Pack skiped" + exit 1 + } +} + +function isPresent() { + [CmdletBinding()] + Param( + [Object[]]$args + ) + Write-Output "[INFO] Check input" + + $present = $args[0] + + if (-not "$present".Equals("1")) { + Write-Output $args[1] + $result = 1 + } +} + +function clearTarget() { + Write-Output "clearTarget ZIP: $ZIP_NAME" + Write-Output "[INFO] Clear target files.$ZIP_NAME." + + if (Test-Path "image.tar") { + Remove-Item "image.tar" + } + if (Test-Path "$global:ZIP_NAME") { + Remove-Item "$global:ZIP_NAME" + } +} + +function buildImage() { + $imagename = $global:IMAGE_NAME + $imagename = $imagename + ":" + $imagename = $imagename + $global:TAG_NAME + + Set-Location .\docker + Write-Output "[INFO] Build image $imagename." + docker build -t $imagename . + Set-Location .. +} + +function exportImage() { + $imagename = $global:IMAGE_NAME + $imagename = $imagename + ":" + $imagename = $imagename + $global:TAG_NAME + Write-Output "[INFO] Export image" + docker save $imagename -o "docker\image.tar" +} + +function zipFile() { + Get-Location + Write-Output "[INFO] Zip file $global:ZIP_NAME" + Compress-Archive -Path .\docker\* -CompressionLevel Fastest -DestinationPath .\$global:ZIP_NAME +} +$deployResult = 0 +function verifyDeployPrerequisits() { + + verifyParamSet "$IMAGE_NAME" "name" + verifyParamSet "$DEPLOY_ADDRESS" "address" + verifyParamSet "$DEPLOY_TENANT" "tenant" + verifyParamSet "$DEPLOY_USER" "user" + verifyParamSet "$DEPLOY_PASSWORD" "password" + + if ("$deployResult".Equals("1")) { + Write-Output "[WARNING] Deployment skiped" + exit 1 + } +} + +function verifyParamSet() { + [CmdletBinding()] + Param( + [string]$param1, + [string]$param2 + ) + + Write-Output "x$param1" + + if ("x$param1".Equals("x")) { + Write-Output "[WARNING] Missing parameter: $param2" + $deployResult = 1 + } +} + +function push() { + + getApplicationId + + if ( "x$global:APPLICATION_ID".Equals("x")) { + Write-Output "[INFO] Application with name $APPLICATION_NAME not found, add new application" + createApplication + getApplicationId + + if ("x$global:APPLICATION_ID".Equals("x")) { + Write-Output "[ERROR] Could not create application" + EXIT 1 + } + } + + Write-Output "aaa authorization $global:APPLICATION_ID" + uploadFile +} + +function getApplicationId() { + + $authorization = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $global:DEPLOY_USER,$global:DEPLOY_PASSWORD))) + + # Write-Output "ApplicationId Uri $DEPLOY_ADDRESS/application/applicationsByName/$APPLICATION_NAME" + # Write-Output "ApplicationId authorization $authorization" + + $Result = Invoke-RestMethod -Uri "$DEPLOY_ADDRESS/application/applicationsByName/$APPLICATION_NAME" ` + -Headers @{Authorization = ("Basic {0}" -f $authorization)} ` + -ErrorVariable RestError -ErrorAction "SilentlyContinue" + + if ($RestError) { + $HttpStatusCode = $RestError.ErrorRecord.Exception.Response.StatusCode.value__ + $HttpStatusDescription = $RestError.ErrorRecord.Exception.Response.StatusDescription + + Write-Output "[ERROR] Error while connecting to platform" + Write-Output "Http Status Code: $($HttpStatusCode) `nHttp Status Description: $($HttpStatusDescription)" + Exit + } + else { + $global:APPLICATION_ID = $Result.applications.id + } +} + +function createApplication() { + + $authorization = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $global:DEPLOY_USER,$global:DEPLOY_PASSWORD))) + $body = "{ + ""name"": ""$APPLICATION_NAME"", + ""type"": ""MICROSERVICE"", + ""key"": ""$APPLICATION_NAME-microservice-key"" + }"; + + $Result = Invoke-RestMethod -Method Post -Uri "$DEPLOY_ADDRESS/application/applications" ` + -Headers @{Authorization = ("Basic {0}" -f $authorization)} ` + -Body $body ` + -ErrorVariable RestError -ErrorAction "SilentlyContinue" ` +} + + +function deploy() { + verifyDeployPrerequisits + push +} + +function uploadFile() { + + Write-Output "[INFO] Upload file $WORK_DIR\$ZIP_NAME" + + $authorization = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $global:DEPLOY_USER,$global:DEPLOY_PASSWORD))) + $uri = [string]::Concat($global:DEPLOY_ADDRESS, "/application/applications/", $global:APPLICATION_ID,"/binaries") + + Write-Output "[INFO] uri-> $uri" + Write-Output "[INFO] authorization-> $authorization" + Write-Output "[INFO] global:DEPLOY_ADDRESS-> $global:DEPLOY_ADDRESS" + Write-Output "[INFO] global:APPLICATION_ID-> $global:APPLICATION_ID" + + try { + + Invoke-MultipartFormDataUpload -InFile "$WORK_DIR\$ZIP_NAME" ` + -Uri $uri ` + -Header $authorization + } + catch { + # Dig into the exception to get the Response details. + # Note that value__ is not a typo. + Write-Host "StatusCode:" $_.Exception.Message + + Write-Host "Response:" $_.Exception.Response + $result = $_.Exception.Response.GetResponseStream() + $reader = New-Object System.IO.StreamReader($result) + $reader.BaseStream.Position = 0 + $reader.DiscardBufferedData() + $responseBody = $reader.ReadToEnd(); + Write-Host "ResponseBody:" $responseBody + } +} + + +function subscribe () { + verifySubscribePrerequisits + + $authorization = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $global:DEPLOY_USER,$global:DEPLOY_PASSWORD))) + + Write-Output "[INFO] authorization { $authorization}" + Write-Output "[INFO] Tenant $DEPLOY_TENANT subscription to application $APPLICATION_NAME with id $APPLICATION_ID" + $body = "{""application"":{""id"": ""$APPLICATION_ID""}}" + + $Result = Invoke-RestMethod -Method Post -Uri "$DEPLOY_ADDRESS/tenant/tenants/$DEPLOY_TENANT/applications" ` + -Headers @{Authorization = ("Basic {0}" -f $authorization)} ` + -Body $body ` + -ContentType "application/json" -ErrorVariable RestError -ErrorAction "SilentlyContinue" + + + if ($RestError) { + $HttpStatusCode = $RestError.ErrorRecord.Exception.Response.StatusCode.value__ + $HttpStatusDescription = $RestError.ErrorRecord.Exception.Response.StatusDescription + + Write-Output "[WARNING] error subscribing tenant to application " + Write-Output "Http Status Code: $($HttpStatusCode) `nHttp Status Description: $($HttpStatusDescription)" + } + else { + Write-Output "[INFO] Tenant $DEPLOY_TENANT subscribed to application $APPLICATION_NAME" + } + +} + +function verifySubscribePrerequisits() { + if ("x$APPLICATION_ID".Equals("x")) { + Write-Output "[ERROR] Subscription not possible uknknown applicaitonId" + exit 1 + } + verifyDeployPrerequisits +} + +function Invoke-MultipartFormDataUpload { + PARAM + ( + [string][parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$InFile, + [string]$ContentType, + [Uri][parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$Uri, + [string] [parameter(Mandatory = $true)] $Header + ) + BEGIN { + if (-not (Test-Path $InFile)) { + $errorMessage = ("File {0} missing or unable to read." -f $InFile) + $exception = New-Object System.Exception $errorMessage + $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, 'MultipartFormDataUpload', ([System.Management.Automation.ErrorCategory]::InvalidArgument), $InFile + $PSCmdlet.ThrowTerminatingError($errorRecord) + } + + if (-not $ContentType) { + Add-Type -AssemblyName System.Web + + $mimeType = [System.Web.MimeMapping]::GetMimeMapping($InFile) + + if ($mimeType) { + $ContentType = $mimeType + } + else { + $ContentType = "application/octet-stream" + } + } + } + PROCESS { + $fileName = Split-Path $InFile -leaf + $boundary = [guid]::NewGuid().ToString() + + $fileBin = [System.IO.File]::ReadAllBytes($InFile) + $enc = [System.Text.Encoding]::GetEncoding("iso-8859-1") + +$template = @' +--{0} +Content-Disposition: form-data; name="fileData"; filename="{1}" +Content-Type: {2} + +{3} +--{0}-- + +'@ + + $body = $template -f $boundary, $fileName, $ContentType, $enc.GetString($fileBin) + + + #Write-Output "[INFO] body: $body" + Write-Output "[INFO] boundary: $boundary" + + try { + return Invoke-WebRequest -Uri $Uri ` + -Method Post ` + -ContentType "multipart/form-data; boundary=$boundary" ` + -Body $body ` + -Headers @{Authorization = ("Basic {0}" -f $Header)} + } + catch [Exception] { + $PSCmdlet.ThrowTerminatingError($_) + } + } + END { } +} + +execute $args + From b7af4aa9fd1bce73b88bd55c65be6b51210be13a Mon Sep 17 00:00:00 2001 From: Kumar Pallav Date: Mon, 7 Sep 2020 23:06:36 +0530 Subject: [PATCH 2/6] Implemented all the review comments --- .gitignore | 5 +++ .../images/multi/cumulocity.json | 6 ++- .../src/DeviceMicroservice/AlarmBody.cs | 17 ++++++++ .../Controllers/ThermostatController.cs | 39 ++++++++++++------- .../src/DeviceMicroservice/Program.cs | 5 ++- .../Properties/launchSettings.json | 3 +- .../src/DeviceMicroservice/Thermometer.cs | 6 +-- .../DeviceTemperature/tools/packages.config | 4 ++ .../ReadMe.md | 3 ++ 9 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/AlarmBody.cs create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/tools/packages.config diff --git a/.gitignore b/.gitignore index 83a4f60..9f12f6c 100644 --- a/.gitignore +++ b/.gitignore @@ -213,3 +213,8 @@ Examples/MicroserviceSDK/Nordpool/.vs/Nordpool/v15/Server/sqlite3/db.lock Examples/MicroserviceSDK/Nordpool/.vs/Nordpool/v15/Server/sqlite3/storage.ide Examples/MicroserviceSDK/Nordpool/.vs/Nordpool/v15/Server/sqlite3/storage.ide-shm Examples/MicroserviceSDK/Nordpool/.vs/Nordpool/v15/Server/sqlite3/storage.ide-wal +Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/.vs +Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/images/multi/image.tar +Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/images/multi/image.zip +Examples/MicroserviceSDK/Nordpool/.vs + diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/images/multi/cumulocity.json b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/images/multi/cumulocity.json index 1a8b4f0..80b6553 100644 --- a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/images/multi/cumulocity.json +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/images/multi/cumulocity.json @@ -1,11 +1,13 @@ { "apiVersion": "1", - "version": "1.1.2" + "version": "0.0.5", "provider": { "name": "Cumulocity GmbH" }, - "contextPath": "helloworld", + "contextPath": "thermomicroservice", "isolation": "PER_TENANT", + "requiredRoles": [ + ], "resources": { "cpu": "2000m", "memory": "2Gi" diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/AlarmBody.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/AlarmBody.cs new file mode 100644 index 0000000..4505a43 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/AlarmBody.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace DeviceMicroservice +{ + public class AlarmPayload + { + public Dictionary source { get; set; } + public string type { get; set; } + public string text { get; set; } + public string severity { get; set; } + public string status { get; set; } + public string time { get; set; } + } +} diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs index 3ffe35a..f59eb99 100644 --- a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs @@ -4,15 +4,15 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Cumulocity.SDK.Microservices.Settings; -using Newtonsoft.Json; using RestSharp; using DeviceMicroservice; using DeviceMicroservice.Controllers; using Microsoft.Extensions.Logging; +using System.Text.Json; namespace ThermostatMicroservice.Controllers { - [Route("api/[controller]")] + [Route("[controller]")] [ApiController] public class ThermostatController : ControllerBase { @@ -26,11 +26,11 @@ public ThermostatController(Platform platform, ILogger> Get() { - var client = new RestClient(PlatformSettings.BASEURL + "/inventory/managedObjects?fragmentType=c8y_IsThermometer&fragmentType=c8y_SupportedMeasurements)"); + var client = new RestClient(PlatformSettings.BASEURL + "/inventory/managedObjects?query =$filter = (has(c8y_IsThermometer) + and + has(c8y_SupportedMeasurements)"); var request = new RestRequest(Method.GET); request.AddHeader("Authorization", Request.Headers["Authorization"]); request.AddHeader("Content-Type", "application/json"); @@ -41,14 +41,17 @@ public async Task> Get() } // This endpoint creates thermometer (temprature measurment supported device in cumulocity) as per the JSON body of the object and sends one temprature measurment which is there in the body. - // POST api/Thermostat/thermometers + // POST Thermostat/thermometers [HttpPost("thermometers")] - public async Task> Post([FromBody] Thermometer thermometer) + public Thermometer Post([FromBody] Thermometer thermometer) { try { if (thermometer == null) - return "Add temprature object json in the request body."; + { + Console.WriteLine("Add temprature object json in the request body."); + return null; + } var client = new RestClient(PlatformSettings.BASEURL+"/inventory/managedObjects"); var request = new RestRequest(Method.POST); @@ -59,12 +62,12 @@ public async Task> Post([FromBody] Thermometer thermometer) IRestResponse response = client.Execute(request); Console.WriteLine((response.StatusCode.ToString().Equals("Created")) ? "Thermometer created !!" : "Thermometer couldn't be created !! \n Below is the response body from cumulocity \n"); Console.WriteLine(response.Content); - return thermometer.ToString(); } catch(Exception ex) { - return ex.ToString(); + Console.WriteLine(ex.ToString()); } + return thermometer; } // We are calling the default weather forcast endpoint and if any returned forecast temprature is greater than 25º then we are raising an alarm with alarmText @@ -77,8 +80,18 @@ public string Post([FromBody]AlarmText alarmText) //Mock of hitting endpoint /weatherforecast to take the latest entry and post the temperatureC value to Cumulocity IoT under the specified device(by id). WeatherForecastController w = new WeatherForecastController(_logger); var forecastList = w.Get(); - var id = Request.RouteValues.Values.ElementAt(0); // To retrive the device ID from the Request URL , which will be later used to create an alarm. - + var id = Request.RouteValues.Values.ElementAt(0); // To retrive the device ID from the Request URL , which will be later used to create an alarm. + + string alarmCreationPayload = JsonSerializer.Serialize(new AlarmPayload + { + source = new Dictionary { { "id", (string)id } }, + type = "c8y_hightemperature_alarm", + text = alarmText.alarmText, + severity = "WARNING", + status = "ACTIVE", + time = "2020-03-03T12:03:27.845Z" + }); + // If any of the forecast is higher than 25º then we raise an alarm for the device ID passed in the request. foreach (var forecast in forecastList) { @@ -90,7 +103,7 @@ public string Post([FromBody]AlarmText alarmText) request.AddHeader("Authorization", Request.Headers["Authorization"]); request.AddHeader("Content-Type", "application/vnd.com.nsn.cumulocity.alarm+json"); request.AddHeader("Accept", "application/vnd.com.nsn.cumulocity.alarm+json"); - request.AddParameter("application/vnd.com.nsn.cumulocity.alarm+json", "{\"source\": {\"id\": \"" + id + "\" },\"type\":\"c8y_hightemperature_alarm\",\"text\":\"" + alarmText.alarmText + "\",\"severity\":\"WARNING\",\"status\":\"ACTIVE\",\"time\":\"2020-03-03T12:03:27.845Z\"}", ParameterType.RequestBody); + request.AddParameter("application/vnd.com.nsn.cumulocity.alarm+json", alarmCreationPayload , ParameterType.RequestBody); IRestResponse response = client.Execute(request); Console.WriteLine(response.Content); return response.Content.ToString(); @@ -100,7 +113,7 @@ public string Post([FromBody]AlarmText alarmText) } catch (Exception e) { - return e.ToString(); + Console.WriteLine(e.ToString()); } return "No High temprature was observed in the forecast. So no alarms were raised. Please hit this endpoint till high temprature is observed."; diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Program.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Program.cs index e175154..f2937ad 100644 --- a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Program.cs +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Program.cs @@ -12,14 +12,15 @@ namespace DeviceMicroservice using System.Net; -using Cumulocity.SDK.Microservices.Configure; + using System.Text.Json; + using Cumulocity.SDK.Microservices.Configure; using Microsoft.AspNetCore; public class Program { public static void Main(string[] args) { - BuildWebHost(args).Run(); + BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Properties/launchSettings.json b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Properties/launchSettings.json index 888ea63..b4f3a46 100644 --- a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Properties/launchSettings.json +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Properties/launchSettings.json @@ -20,8 +20,7 @@ "DeviceMicroservice": { "commandName": "Project", "launchBrowser": true, - // "launchUrl": "weatherforecast", - "launchUrl": "api/device", + "launchUrl": "thermostat", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "SERVER_PORT": "47000", diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Thermometer.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Thermometer.cs index 693e2c6..1149d4b 100644 --- a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Thermometer.cs +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Thermometer.cs @@ -9,13 +9,9 @@ public class Thermometer { public string nameID { get; set; } - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - public override string ToString() { - return "Thermometer Name : " + nameID + "\nTemperatureC: " + TemperatureC + "\nTemperatureF: " + TemperatureF; + return "Thermometer Name : " + nameID ; } } diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/tools/packages.config b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/tools/packages.config new file mode 100644 index 0000000..cedcc6a --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/tools/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md index 0fe803e..7d87d43 100644 --- a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md @@ -22,6 +22,7 @@ This scenario has smart thermometers that send readings to Cumulocity IoT. This - Endpoint - 1 Implement the endpoint //thermometers +Example of full endpoint - /service/thermomicroservice/api/Thermostat/thermometers Action: Register a new thermometer Method: POST Payload: @@ -44,12 +45,14 @@ The custom fragment `c8y_IsThermometer` is used to identify the devices as therm - Endpoint - 2 Implement the endpoint //thermometers +Example of full endpoint - /service/thermomicroservice/api/Thermostat/thermometers Action: Retrieve all registered thermometers Method: GET internally the microservice calls /inventory/managedObjects?fragmentType=c8y_IsThermometer&fragmentType=c8y_SupportedMeasurements - Endpoint - 3 Implement the endpoint /thermometers/{id}/temperatures/ +Example of full endpoint - /service/thermomicroservice/thermometers//temperatures Method: POST Payload: { From bc3411656814e3bf25731cf62013fb08fedf16ed Mon Sep 17 00:00:00 2001 From: Kumar Pallav Date: Tue, 8 Sep 2020 09:41:03 +0530 Subject: [PATCH 3/6] proper routes --- .../src/DeviceMicroservice/Controllers/ThermostatController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs index f59eb99..1b02b38 100644 --- a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs @@ -72,7 +72,7 @@ public Thermometer Post([FromBody] Thermometer thermometer) // We are calling the default weather forcast endpoint and if any returned forecast temprature is greater than 25º then we are raising an alarm with alarmText // POST thermometers//temperatures - [HttpPost("/thermometers/{id}/temperatures")] + [HttpPost("thermometers/{id}/temperatures")] public string Post([FromBody]AlarmText alarmText) { try From 059c0a64f8dc984ed78826bc2494652a225d9e10 Mon Sep 17 00:00:00 2001 From: Kumar Pallav Date: Tue, 8 Sep 2020 13:32:57 +0530 Subject: [PATCH 4/6] Review commet-2 : Thermometer added --- .../Controllers/ThermostatController.cs | 10 +++++++++- .../src/DeviceMicroservice/ThermometerBody.cs | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/ThermometerBody.cs diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs index 1b02b38..d0a0447 100644 --- a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/Controllers/ThermostatController.cs @@ -53,12 +53,20 @@ public Thermometer Post([FromBody] Thermometer thermometer) return null; } + string thermometerCreationPayload = JsonSerializer.Serialize(new ThermometerBody + { + name = thermometer.nameID, + c8y_IsDevice = new Dictionary { }, + c8y_IsThermometer = new Dictionary { }, + c8y_SupportedMeasurements = new string[] { "c8y_TemperatureMeasurement" } + }); + var client = new RestClient(PlatformSettings.BASEURL+"/inventory/managedObjects"); var request = new RestRequest(Method.POST); request.AddHeader("Authorization", Request.Headers["Authorization"]); request.AddHeader("Content-Type", "application/json"); request.AddHeader("Accept", "application/json"); - request.AddParameter("application/json", "{\"name\": \"" + thermometer.nameID + "\", \"c8y_IsDevice\": {}, \"c8y_IsThermometer\": {},\"c8y_SupportedMeasurements\": [\"c8y_TemperatureMeasurement\"]}", ParameterType.RequestBody); + request.AddParameter("application/json", thermometerCreationPayload, ParameterType.RequestBody); IRestResponse response = client.Execute(request); Console.WriteLine((response.StatusCode.ToString().Equals("Created")) ? "Thermometer created !!" : "Thermometer couldn't be created !! \n Below is the response body from cumulocity \n"); Console.WriteLine(response.Content); diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/ThermometerBody.cs b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/ThermometerBody.cs new file mode 100644 index 0000000..29e4664 --- /dev/null +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/DeviceTemperature/src/DeviceMicroservice/ThermometerBody.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace DeviceMicroservice +{ + public class ThermometerBody + { + public string name { get; set; } + public Dictionary c8y_IsDevice { get; set; } + public Dictionary c8y_IsThermometer { get; set; } + public string[] c8y_SupportedMeasurements { get; set; } + + } + +} From 03037e4feeeaccf39776f2a4b16b8963abf5689f Mon Sep 17 00:00:00 2001 From: Kumar Pallav Date: Tue, 8 Sep 2020 15:10:58 +0530 Subject: [PATCH 5/6] Readme changes --- .../Hello-World-Extension-Microservice/ReadMe.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md index 7d87d43..7ccce55 100644 --- a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md @@ -22,13 +22,12 @@ This scenario has smart thermometers that send readings to Cumulocity IoT. This - Endpoint - 1 Implement the endpoint //thermometers -Example of full endpoint - /service/thermomicroservice/api/Thermostat/thermometers +Example of full endpoint - /service/thermomicroservice/Thermostat/thermometers Action: Register a new thermometer Method: POST Payload: { - "nameID" : "", - "TemperatureC" : int + "nameID" : "" } The endpoint internally calls /inventory/managedObjects POST request with the below mentioned payload to create a thermometer in the platform - @@ -45,13 +44,13 @@ The custom fragment `c8y_IsThermometer` is used to identify the devices as therm - Endpoint - 2 Implement the endpoint //thermometers -Example of full endpoint - /service/thermomicroservice/api/Thermostat/thermometers +Example of full endpoint - /service/thermomicroservice/Thermostat/thermometers Action: Retrieve all registered thermometers Method: GET -internally the microservice calls /inventory/managedObjects?fragmentType=c8y_IsThermometer&fragmentType=c8y_SupportedMeasurements +internally the microservice calls /inventory/managedObjects?query =$filter=(has(c8y_IsThermometer) + and + has(c8y_SupportedMeasurements) - Endpoint - 3 -Implement the endpoint /thermometers/{id}/temperatures/ +Implement the endpoint //thermometers/{id}/temperatures/ Example of full endpoint - /service/thermomicroservice/thermometers//temperatures Method: POST Payload: From af56e195ea914214bf62f95730e17f8052beb6fb Mon Sep 17 00:00:00 2001 From: Kumar Pallav Date: Tue, 8 Sep 2020 15:44:27 +0530 Subject: [PATCH 6/6] Endpoint modification --- .../Hello-World-Extension-Microservice/ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md index 7ccce55..55495b3 100644 --- a/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md +++ b/Examples/MicroserviceSDK/Hello-World-Extension-Microservice/ReadMe.md @@ -51,7 +51,7 @@ internally the microservice calls /inventory/managedObjects?query =$filter=(has( - Endpoint - 3 Implement the endpoint //thermometers/{id}/temperatures/ -Example of full endpoint - /service/thermomicroservice/thermometers//temperatures +Example of full endpoint - /service/thermomicroservice/Thermostat/thermometers//temperatures Method: POST Payload: {