NONRTRIC SME fix servicemanager sonar 66/13466/1 master
authoraravind.est <aravindhan.a@est.tech>
Fri, 27 Sep 2024 10:22:16 +0000 (11:22 +0100)
committeraravind.est <aravindhan.a@est.tech>
Fri, 27 Sep 2024 10:22:16 +0000 (11:22 +0100)
Servicemanager sonar job missing the go.mod and it is fixed.

Issue-ID: NONRTRIC-1033
Change-Id: I86b3b74c1a34a9b2b7cc263ab545b939012350f1
Signed-off-by: aravind.est <aravindhan.a@est.tech>
15 files changed:
capifcore/build-capifcore-ubuntu.sh
capifcore/container-tag.yaml
docs/overview.rst
docs/release-notes.rst
servicemanager/.env.example
servicemanager/README.md
servicemanager/build-servicemanager-ubuntu.sh
servicemanager/container-tag.yaml
servicemanager/internal/discoverservice/discoverservice_test.go
servicemanager/internal/envreader/envreader.go
servicemanager/internal/publishservice/publishservice.go
servicemanager/internal/publishservice/publishservice_test.go
servicemanager/internal/publishserviceapi/typeupdate.go
servicemanager/main.go
servicemanager/mockkong/kong_mock.go

index 9bac724..934341f 100755 (executable)
@@ -1,7 +1,8 @@
 #!/bin/bash
 ##############################################################################
 #
 #!/bin/bash
 ##############################################################################
 #
-#   Copyright (C) 2022: Nordix Foundation
+#   Copyright (C) 2022-2023: Nordix Foundation
+#   Copyright (C) 2023-2024: OpenInfra Foundation Europe
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
 #
 #   Licensed under the Apache License, Version 2.0 (the "License");
 #   you may not use this file except in compliance with the License.
@@ -28,7 +29,7 @@ go version
 cd capifcore/
 
 # install the go coverage tool helper
 cd capifcore/
 
 # install the go coverage tool helper
-go install github.com/ory/go-acc
+go install github.com/ory/go-acc@v0.2.8
 
 go get github.com/stretchr/testify/mock@v1.7.1
 
 
 go get github.com/stretchr/testify/mock@v1.7.1
 
index f17eeca..9b9cc34 100644 (file)
@@ -20,4 +20,4 @@
 # By default this file is in the docker build directory,
 # but the location can configured in the JJB template.
 ---
 # By default this file is in the docker build directory,
 # but the location can configured in the JJB template.
 ---
-tag: 1.3.1
+tag: 1.4.0
index c9b42aa..18fc2bb 100644 (file)
@@ -118,7 +118,7 @@ Service Manager Deployment
 
 Postman
 *******
 
 Postman
 *******
-A Postman collection has been included in this repo at sme/postman/ServiceManager.postman_collection.json.
+A Postman collection has been included in this repo at sme/postman/ServiceManager.postman_collection.json
 
 
 *************
 
 
 *************
index e4a93f9..b8b51d0 100644 (file)
@@ -10,35 +10,45 @@ Release Notes
 
 This document provides the release notes for the Non-RT RIC Service Management & Exposure (SME).
 
 
 This document provides the release notes for the Non-RT RIC Service Management & Exposure (SME).
 
-Version history SME CAPIFcore
-=============================
+Version history SME Service Manager
+===================================
 
 +------------+----------+------------------+--------------------------------------+
 | **Date**   | **Ver.** | **Author**       | **Comment**                          |
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
 
 +------------+----------+------------------+--------------------------------------+
 | **Date**   | **Ver.** | **Author**       | **Comment**                          |
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
-| 2022-12-14 | 1.0.0    | Henrik Andersson | G Release                            |
-|            |          |                  | Initial version of Capifcore         |
+| 2024-06-26 | 0.1.2    |  Denis G Noonan  | J Release (Service Manager)          |
+|            |          |                  | Initial version of Service Manager   |
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
-| 2023-02-10 | 1.0.1    | Yennifer Chacon  | G Maintenance                        |
-|            |          |                  | Release                              |
+| TBA        | TBA      |  Denis G Noonan  | Service Manager includes support for |
+|            |          |                  | dynamic URIs, multiple interface     |
+|            |          |                  | descriptions, and improved schema    |
+|            |          |                  | validation.                          |
++------------+----------+------------------+--------------------------------------+
+
+Version history SME CAPIFCore
+=============================
+
 +------------+----------+------------------+--------------------------------------+
 +------------+----------+------------------+--------------------------------------+
-| 2023-06-16 | 1.1.0    | Yennifer Chacon  | H Release                            |
+| **Date**   | **Ver.** | **Author**       | **Comment**                          |
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
-| 2023-12-15 | 1.2.0    | John Keeney      | I Release                            |
+| 2022-12-14 | 1.0.0    | Henrik Andersson | G Release                            |
+|            |          |                  | Initial version of CapifCore         |
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
-
-Version history SME Service Manager
-===================================
-
+| 2023-02-10 | 1.0.1    | Yennifer Chacon  | G Maintenance Release (CapifCore)    |
+|            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
 +------------+----------+------------------+--------------------------------------+
-| **Date**   | **Ver.** | **Author**       | **Comment**                          |
+| 2023-06-16 | 1.1.0    | Yennifer Chacon  | H Release (CapifCore)                |
+|            |          |                  |                                      |
++------------+----------+------------------+--------------------------------------+
+| 2023-12-15 | 1.2.0    | John Keeney      | I Release (CapifCore)                |
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
 |            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
-| TBA        |          |                  | Initial release of Service Manager   |
+| 2024-06-26 | 1.3.1    | John Keeney      | J Release (CapifCore)                |
+|            |          |                  |                                      |
 +------------+----------+------------------+--------------------------------------+
 
 Release Data
 +------------+----------+------------------+--------------------------------------+
 
 Release Data
@@ -100,7 +110,7 @@ H Release
 | **Release date**            | 2023-06-16                                        |
 |                             |                                                   |
 +-----------------------------+---------------------------------------------------+
 | **Release date**            | 2023-06-16                                        |
 |                             |                                                   |
 +-----------------------------+---------------------------------------------------+
-| **Purpose of the delivery** | nonrtric-plt-capifcore:1.1.0                      |
+| **Purpose of the delivery** | o-ran-sc/nonrtric-plt-capifcore:1.1.0             |
 |                             |    Add more CAPIF core functions and APIs         |
 |                             |                                                   |
 +-----------------------------+---------------------------------------------------+
 |                             |    Add more CAPIF core functions and APIs         |
 |                             |                                                   |
 +-----------------------------+---------------------------------------------------+
@@ -121,7 +131,47 @@ I Release
 | **Release date**            | 2023-12-15                                        |
 |                             |                                                   |
 +-----------------------------+---------------------------------------------------+
 | **Release date**            | 2023-12-15                                        |
 |                             |                                                   |
 +-----------------------------+---------------------------------------------------+
-| **Purpose of the delivery** | nonrtric-plt-capifcore:1.2.0                      |
+| **Purpose of the delivery** | o-ran-sc/nonrtric-plt-capifcore:1.2.0             |
 |                             |    Add more CAPIF core functions and APIs         |
 |                             |                                                   |
 +-----------------------------+---------------------------------------------------+
 |                             |    Add more CAPIF core functions and APIs         |
 |                             |                                                   |
 +-----------------------------+---------------------------------------------------+
+
+J Release
+---------
++-----------------------------+---------------------------------------------------+
+| **Project**                 | Non-RT RIC Service Manager                        |
+|                             |                                                   |
++-----------------------------+---------------------------------------------------+
+| **Repo/commit-ID**          | nonrtric/plt/sme/                                 |
+|                             | 36718abc0fb386770a182c2c01358e1ce3621c75          |
+|                             |                                                   |
++-----------------------------+---------------------------------------------------+
+| **Release designation**     | J release                                         |
+|                             |                                                   |
++-----------------------------+---------------------------------------------------+
+| **Release date**            | 2024-06-26                                        |
+|                             |                                                   |
++-----------------------------+---------------------------------------------------+
+| **Purpose of the delivery** | o-ran-sc/nonrtric-plt-servicemanager:0.1.2        |
+|                             |    First release of NONRTRIC Service Manager      |
+|                             |                                                   |
++-----------------------------+---------------------------------------------------+
+
++-----------------------------+---------------------------------------------------+
+| **Project**                 | Non-RT RIC CAPIF Core                             |
+|                             |                                                   |
++-----------------------------+---------------------------------------------------+
+| **Repo/commit-ID**          | nonrtric/plt/sme/                                 |
+|                             | 36718abc0fb386770a182c2c01358e1ce3621c75          |
+|                             |                                                   |
++-----------------------------+---------------------------------------------------+
+| **Release designation**     | J release                                         |
+|                             |                                                   |
++-----------------------------+---------------------------------------------------+
+| **Release date**            | 2024-06-26                                        |
+|                             |                                                   |
++-----------------------------+---------------------------------------------------+
+| **Purpose of the delivery** | o-ran-sc/nonrtric-plt-capifcore:1.3.1             |
+|                             |    Small updates & improvements                   |
+|                             |                                                   |
++-----------------------------+---------------------------------------------------+
index 1d3c4ef..c5d5b32 100644 (file)
@@ -8,7 +8,7 @@ KONG_CONTROL_PLANE_PORT=<port number>
 KONG_DATA_PLANE_IPV4=<host string>
 KONG_DATA_PLANE_PORT=<port number>
 CAPIF_PROTOCOL=<http or https protocol scheme>
 KONG_DATA_PLANE_IPV4=<host string>
 KONG_DATA_PLANE_PORT=<port number>
 CAPIF_PROTOCOL=<http or https protocol scheme>
-CAPIF_IPV4=<host>
+CAPIF_IPV4=<host string>
 CAPIF_PORT=<port number>
 LOG_LEVEL=<Trace, Debug, Info, Warning, Error, Fatal or Panic>
 SERVICE_MANAGER_PORT=<port number>
 CAPIF_PORT=<port number>
 LOG_LEVEL=<Trace, Debug, Info, Warning, Error, Fatal or Panic>
 SERVICE_MANAGER_PORT=<port number>
index 4be8951..c322bae 100644 (file)
@@ -3,7 +3,7 @@
 ========================LICENSE_START=================================
 O-RAN-SC
 %%
 ========================LICENSE_START=================================
 O-RAN-SC
 %%
-Copyright (C) 2024 OpenInfra Foundation Europe. All rights reserved.
+Copyright (C) 2024 OpenInfra Foundation Europe. All rights reserved
 %%
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 %%
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -63,7 +63,7 @@ export SERVICE_MANAGER_ENV=development
 
 ### CAPIFcore and Kong
 
 
 ### CAPIFcore and Kong
 
-We also need Kong and CAPIFcore to be running. Please see the examples in the `deploy` folder. You can also use https://gerrit.o-ran-sc.org/r/it/dep for deployment. Please see the notes at https://wiki.o-ran-sc.org/display/RICNR/%5BWIP%5D+Service+Manager.
+We also need Kong and CAPIFcore to be running. Please see the examples in the `deploy` folder. You can also use https://gerrit.o-ran-sc.org/r/it/dep for deployment. Please see the notes at https://wiki.o-ran-sc.org/display/RICNR/Release+J%3A+Service+Manager
 
 ## Build
 
 
 ## Build
 
@@ -145,4 +145,63 @@ The additional env file needs to exist in the sme/servicemanager folder so that
 
 ## Postman
 
 
 ## Postman
 
-A Postman collection has been included in this repo at sme/postman/ServiceManager.postman_collection.json.
\ No newline at end of file
+A Postman collection has been included in this repo at sme/postman/ServiceManager.postman_collection.json.
+
+## Interface Descriptions
+
+To distinguish between multiple interface descriptions, Service Manager prepends the port number and a hash code to the URL path.
+
+## Static and Dynamic Routes
+
+We can specify either static or dynamic routes. Static routing defines a route when there is a single route for traffic to reach a destination. Dynamic routing allows us to specify path parameters. In this config file, we specify path parameters using regular expressions.
+
+Kong uses the regex definition from the [Rust programming language](https://docs.rs/regex/latest/regex/) to specify the regular expression (regex) that describes the path parameters, [Kong regex](https://docs.konghq.com/gateway/latest/key-concepts/routes/#regular-expressions).
+
+An example of a static path is as follows. This is the straightforward case.
+
+```http
+   /rapps
+```
+
+An example of a dynamic path is
+
+```http
+   ~/rapps/(?<rappId>[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)
+```
+
+Our dynamic path starts with a ~ character. In this example, we have a path parameter that is described by a regex capture group called rappId. The regex describes a word made of mixed-case alphanumeric characters optionally followed by one or more sets of a dash or underscore together with another word.
+
+When the Service Manager client calls a dynamic API, we call the URL without the '~'. Kong substitutes the path parameter according to the rules specified in the regex. Therefore, we can call the above example by using
+
+```http
+   /rapps/my-rApp-id
+```
+
+as the URL where my-rApp-id is the rApp id of in the rApp Manager. The name my-rApp-id has to match the regex shown above.
+
+It is required to name the capture group in this YAML config file. The capture group name is used by Service Manager when creating a Kong Request Transformer plugin. We can specify multiple capture groups in a URL if there are multiple path parameters in the API path.
+
+We create a Kong Request Transformer plugin with .data[].config.replace, as in the following example curl with abridged response.
+
+```sh
+curl -X GET http://oran-nonrtric-kong-admin.nonrtric.svc.cluster.local:8001/plugins
+```
+
+```json
+{
+  "body": [],
+  "uri": "/rapps/$(uri_captures[\"rappId\"])",
+  "headers": [],
+  "querystring": []
+}
+```
+
+In our example, this allows Kong to match /rapps/my-rApp-id.
+
+The Service Manager uses the following regex to search and replace the YAML file regexes.
+
+```regex
+/\(\?<([^>]+)>([^\/]+)/
+```
+
+Please note that the example path, /rapps/my-rApp-id, is not terminated by a '/'. Service Manager adds a '/' for internal matching. This made the regex easier to develop. Service Manager will match on /rapps/my-rApp-id/ for this case.
index 963e3aa..bcc6481 100755 (executable)
@@ -25,9 +25,10 @@ echo "--> build-servicemanager-ubuntu.sh"
 export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
 export GO111MODULE=on
 go version
 export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
 export GO111MODULE=on
 go version
+cd servicemanager/
 
 # Get the go coverage tool helper
 
 # Get the go coverage tool helper
-go install github.com/ory/go-acc
+go install github.com/ory/go-acc@v0.2.8
 go get github.com/stretchr/testify/mock@v1.7.1
 go-acc ./... --ignore mockkong,common,discoverserviceapi,invokermanagementapi,publishserviceapi,providermanagementapi
 
 go get github.com/stretchr/testify/mock@v1.7.1
 go-acc ./... --ignore mockkong,common,discoverserviceapi,invokermanagementapi,publishserviceapi,providermanagementapi
 
index ced992f..fdabad8 100644 (file)
@@ -19,4 +19,4 @@
 # By default this file is in the docker build directory,
 # but the location can configured in the JJB template.
 ---
 # By default this file is in the docker build directory,
 # but the location can configured in the JJB template.
 ---
-tag: 0.1.2
+tag: 0.2.0
index ab6ae85..3b6fa81 100644 (file)
@@ -731,16 +731,14 @@ func registerHandlers(e *echo.Echo, myEnv map[string]string, myPorts map[string]
 func getServiceAPIDescription(aefId string, apiName string, apiCategory string, apiVersion string, protocol *publishapi.Protocol, dataFormat *publishapi.DataFormat, description string, testServiceIpv4 common29122.Ipv4Addr, testServicePort common29122.Port, commType publishapi.CommunicationType) publishapi.ServiceAPIDescription {
        domainName := "Kong"
        otherDomainName := "otherDomain"
 func getServiceAPIDescription(aefId string, apiName string, apiCategory string, apiVersion string, protocol *publishapi.Protocol, dataFormat *publishapi.DataFormat, description string, testServiceIpv4 common29122.Ipv4Addr, testServicePort common29122.Port, commType publishapi.CommunicationType) publishapi.ServiceAPIDescription {
        domainName := "Kong"
        otherDomainName := "otherDomain"
-
        var otherProtocol publishapi.Protocol = "HTTP_2"
        var otherProtocol publishapi.Protocol = "HTTP_2"
+       var DataFormatOther publishapi.DataFormat = "OTHER"
 
        categoryPointer := &apiCategory
        if apiCategory == "" {
                categoryPointer = nil
        }
 
 
        categoryPointer := &apiCategory
        if apiCategory == "" {
                categoryPointer = nil
        }
 
-       var DataFormatOther publishapi.DataFormat = "OTHER"
-
        return publishapi.ServiceAPIDescription{
                AefProfiles: &[]publishapi.AefProfile{
                        {
        return publishapi.ServiceAPIDescription{
                AefProfiles: &[]publishapi.AefProfile{
                        {
@@ -775,6 +773,15 @@ func getServiceAPIDescription(aefId string, apiName string, apiCategory string,
                        },
                        {
                                AefId:      aefId, // "otherAefId"
                        },
                        {
                                AefId:      aefId, // "otherAefId"
+                               InterfaceDescriptions: &[]publishapi.InterfaceDescription{
+                                       {
+                                               Ipv4Addr: &testServiceIpv4,
+                                               Port:     &testServicePort,
+                                               SecurityMethods: &[]publishapi.SecurityMethod{
+                                                       "PSK",
+                                               },
+                                       },
+                               },
                                DomainName: &otherDomainName,
                                Protocol:   &otherProtocol,
                                DataFormat: &DataFormatOther,
                                DomainName: &otherDomainName,
                                Protocol:   &otherProtocol,
                                DataFormat: &DataFormatOther,
index f08b1d7..315772e 100644 (file)
@@ -21,6 +21,8 @@
 package envreader
 
 import (
 package envreader
 
 import (
+       "fmt"
+       "net/url"
        "os"
        "path/filepath"
        "runtime"
        "os"
        "path/filepath"
        "runtime"
@@ -75,6 +77,14 @@ func (r *RealConfigReader) ReadDotEnv() (map[string]string, map[string]int, erro
        logConfig(myEnv, envFile)
 
        myPorts, err := createMapPorts(myEnv)
        logConfig(myEnv, envFile)
 
        myPorts, err := createMapPorts(myEnv)
+       if err == nil {
+               err = validateEnv(myEnv)
+       }
+
+       if err == nil {
+               err = validateUrls(myEnv, myPorts)
+       }
+
        return myEnv, myPorts, err
 }
 
        return myEnv, myPorts, err
 }
 
@@ -110,31 +120,98 @@ func logConfig(myEnv map[string]string, envFile string) {
        log.Infof("TEST_SERVICE_PORT %s", myEnv["TEST_SERVICE_PORT"])
 }
 
        log.Infof("TEST_SERVICE_PORT %s", myEnv["TEST_SERVICE_PORT"])
 }
 
+func validateUrls(myEnv map[string]string, myPorts map[string]int) error {
+       capifProtocol := myEnv["CAPIF_PROTOCOL"]
+       capifIPv4 := myEnv["CAPIF_IPV4"]
+       capifPort := myPorts["CAPIF_PORT"]
+       capifcoreUrl := fmt.Sprintf("%s://%s:%d", capifProtocol, capifIPv4, capifPort)
+
+       kongProtocol := myEnv["KONG_PROTOCOL"]
+       kongControlPlaneIPv4 := myEnv["KONG_CONTROL_PLANE_IPV4"]
+       kongControlPlanePort := myPorts["KONG_CONTROL_PLANE_PORT"]
+       kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongControlPlaneIPv4, kongControlPlanePort)
+
+       kongDataPlaneIPv4 := myEnv["KONG_DATA_PLANE_IPV4"]
+       kongDataPlanePort := myPorts["KONG_DATA_PLANE_PORT"]
+       kongDataPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongDataPlaneIPv4, kongDataPlanePort)
+
+       log.Infof("Capifcore URL %s", capifcoreUrl)
+       log.Infof("Kong Control Plane URL %s", kongControlPlaneURL)
+       log.Infof("Kong Data Plane URL %s", kongDataPlaneURL)
+
+       // Very basic checks
+       _, err := url.ParseRequestURI(capifcoreUrl)
+       if err != nil {
+               err = fmt.Errorf("error parsing Capifcore URL: %s", err)
+               return err
+       }
+       _, err = url.ParseRequestURI(kongControlPlaneURL)
+       if err != nil {
+               err = fmt.Errorf("error parsing Kong Control Plane URL: %s", err)
+               return err
+       }
+       _, err = url.ParseRequestURI(kongDataPlaneURL)
+       if err != nil {
+               err = fmt.Errorf("error parsing Kong Data Plane URL: %s", err)
+               return err
+       }
+
+       return nil
+}
+
+func validateEnv(myEnv map[string]string) error {
+       var err error = nil
+
+       kongDomain := myEnv["KONG_DOMAIN"]
+       kongProtocol := myEnv["KONG_PROTOCOL"]
+       kongControlPlaneIPv4 := myEnv["KONG_CONTROL_PLANE_IPV4"]
+       kongDataPlaneIPv4 := myEnv["KONG_DATA_PLANE_IPV4"]
+       capifProtocol := myEnv["CAPIF_PROTOCOL"]
+       capifIPv4 := myEnv["CAPIF_IPV4"]
+
+       if kongDomain == "" || kongDomain == "<string>" {
+               err = fmt.Errorf("error loading KONG_DOMAIN from .env file: %s", kongDomain)
+       } else if kongProtocol == "" || kongProtocol == "<http or https protocol scheme>" {
+               err = fmt.Errorf("error loading KONG_PROTOCOL from .env file: %s", kongProtocol)
+       } else if kongControlPlaneIPv4 == "" || kongControlPlaneIPv4 == "<host string>" {
+               err = fmt.Errorf("error loading KONG_CONTROL_PLANE_IPV4 from .env file: %s", kongControlPlaneIPv4)
+       } else if kongDataPlaneIPv4 == "" || kongDataPlaneIPv4 == "<host string>" {
+               err = fmt.Errorf("error loading KONG_DATA_PLANE_IPV4 from .env file: %s", kongDataPlaneIPv4)
+       } else if capifProtocol == "" || capifProtocol == "<http or https protocol scheme>" {
+               err = fmt.Errorf("error loading CAPIF_PROTOCOL from .env file: %s", capifProtocol)
+       } else if capifIPv4 == "" || capifIPv4 == "<host string>" || capifIPv4 == "<host>" {
+               err = fmt.Errorf("error loading CAPIF_IPV4 from .env file: %s", capifIPv4)
+       }
+       // TEST_SERVICE_IPV4 is used only by the unit tests and are validated in the unit tests.
+
+       return err
+}
+
 func createMapPorts(myEnv map[string]string) (map[string]int, error) {
     myPorts := make(map[string]int)
        var err error
 
        myPorts["KONG_DATA_PLANE_PORT"], err = strconv.Atoi(myEnv["KONG_DATA_PLANE_PORT"])
        if err != nil {
 func createMapPorts(myEnv map[string]string) (map[string]int, error) {
     myPorts := make(map[string]int)
        var err error
 
        myPorts["KONG_DATA_PLANE_PORT"], err = strconv.Atoi(myEnv["KONG_DATA_PLANE_PORT"])
        if err != nil {
-               log.Fatalf("error loading KONG_DATA_PLANE_PORT from .env file: %s", err)
+               err = fmt.Errorf("error loading KONG_DATA_PLANE_PORT from .env file: %s", err)
                return nil, err
        }
 
        myPorts["KONG_CONTROL_PLANE_PORT"], err = strconv.Atoi(myEnv["KONG_CONTROL_PLANE_PORT"])
        if err != nil {
                return nil, err
        }
 
        myPorts["KONG_CONTROL_PLANE_PORT"], err = strconv.Atoi(myEnv["KONG_CONTROL_PLANE_PORT"])
        if err != nil {
-               log.Fatalf("error loading KONG_CONTROL_PLANE_PORT from .env file: %s", err)
+               err = fmt.Errorf("error loading KONG_CONTROL_PLANE_PORT from .env file: %s", err)
                return nil, err
        }
 
        myPorts["CAPIF_PORT"], err = strconv.Atoi(myEnv["CAPIF_PORT"])
        if err != nil {
                return nil, err
        }
 
        myPorts["CAPIF_PORT"], err = strconv.Atoi(myEnv["CAPIF_PORT"])
        if err != nil {
-               log.Fatalf("error loading CAPIF_PORT from .env file: %s", err)
+               err = fmt.Errorf("error loading CAPIF_PORT from .env file: %s", err)
                return nil, err
        }
 
        myPorts["SERVICE_MANAGER_PORT"], err = strconv.Atoi(myEnv["SERVICE_MANAGER_PORT"])
        if err != nil {
                return nil, err
        }
 
        myPorts["SERVICE_MANAGER_PORT"], err = strconv.Atoi(myEnv["SERVICE_MANAGER_PORT"])
        if err != nil {
-               log.Fatalf("error loading SERVICE_MANAGER_PORT from .env file: %s", err)
+               err = fmt.Errorf("error loading SERVICE_MANAGER_PORT from .env file: %s", err)
                return nil, err
        }
 
                return nil, err
        }
 
@@ -142,7 +219,7 @@ func createMapPorts(myEnv map[string]string) (map[string]int, error) {
        if myEnv["TEST_SERVICE_PORT"] != "" {
                myPorts["TEST_SERVICE_PORT"], err = strconv.Atoi(myEnv["TEST_SERVICE_PORT"])
                if err != nil {
        if myEnv["TEST_SERVICE_PORT"] != "" {
                myPorts["TEST_SERVICE_PORT"], err = strconv.Atoi(myEnv["TEST_SERVICE_PORT"])
                if err != nil {
-                       log.Fatalf("error loading TEST_SERVICE_PORT from .env file: %s", err)
+                       err = fmt.Errorf("error loading TEST_SERVICE_PORT from .env file: %s", err)
                        return nil, err
                }
        }
                        return nil, err
                }
        }
index 4031827..d042cd0 100644 (file)
@@ -103,10 +103,18 @@ func (ps *PublishService) PostApfIdServiceApis(ctx echo.Context, apfId string) e
                        ps.KongDataPlaneIPv4,
                        ps.KongDataPlanePort,
                        apfId)
                        ps.KongDataPlaneIPv4,
                        ps.KongDataPlanePort,
                        apfId)
-       if (err != nil) || (statusCode != http.StatusCreated) {
-               // We can return with http.StatusForbidden if there is a http.StatusConflict detected by Kong
+
+       log.Trace("After RegisterKong")
+
+       if err != nil {
                msg := err.Error()
                msg := err.Error()
-               log.Errorf("error on RegisterKong %s", msg)
+               log.Errorf("PostApfIdServiceApis, error on RegisterKong %s", msg)
+               return sendCoreError(ctx, statusCode, msg)
+       }
+       if  statusCode != http.StatusCreated {
+               // We can return with http.StatusForbidden if there is a http.StatusConflict detected by Kong
+               msg := "error detected by Kong"
+               log.Errorf(msg)
                return sendCoreError(ctx, statusCode, msg)
        }
 
                return sendCoreError(ctx, statusCode, msg)
        }
 
index c42cba7..eb33c38 100644 (file)
@@ -202,6 +202,24 @@ func capifCleanUp() {
        result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
        assert.Equal(t, http.StatusNoContent, result.Code())
 
        result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
        assert.Equal(t, http.StatusNoContent, result.Code())
 
+       apiName = "helloworld-v1"
+       apiId = "api_id_" + apiName
+
+       result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
+       assert.Equal(t, http.StatusNoContent, result.Code())
+
+       apiName = "helloworld-v1-id"
+       apiId = "api_id_" + apiName
+
+       result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
+       assert.Equal(t, http.StatusNoContent, result.Code())
+
+       apiName = "helloworld-no-version"
+       apiId = "api_id_" + apiName
+
+       result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+apiId).Go(t, eServiceManager)
+       assert.Equal(t, http.StatusNoContent, result.Code())
+
        // Delete the provider
        domainID := "domain_id_Kong"
        result = testutil.NewRequest().Delete("/api-provider-management/v1/registrations/"+domainID).Go(t, eServiceManager)
        // Delete the provider
        domainID := "domain_id_Kong"
        result = testutil.NewRequest().Delete("/api-provider-management/v1/registrations/"+domainID).Go(t, eServiceManager)
@@ -262,7 +280,11 @@ func TestPostUnpublishedServiceWithUnregisteredPublisher(t *testing.T) {
        assert.NotZero(t, testServicePort, "TEST_SERVICE_PORT is required in .env file for unit testing")
 
        apiName := "apiName"
        assert.NotZero(t, testServicePort, "TEST_SERVICE_PORT is required in .env file for unit testing")
 
        apiName := "apiName"
-       newServiceDescription := getServiceAPIDescription(aefId, apiName, description, testServiceIpv4, testServicePort)
+       apiVersion := "v1"
+       resourceName := "helloworld"
+       uri := "/helloworld"
+
+       newServiceDescription := getServiceAPIDescription(aefId, apiName, description, testServiceIpv4, testServicePort, apiVersion, resourceName, uri)
 
        // Attempt to publish a service for provider
        result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
 
        // Attempt to publish a service for provider
        result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
@@ -288,7 +310,7 @@ func TestRegisterValidProvider(t *testing.T) {
        assert.NoError(t, err, "error unmarshaling response")
 }
 
        assert.NoError(t, err, "error unmarshaling response")
 }
 
-func TestPublishUnpublishService(t *testing.T) {
+func TestPublishUnpublishServiceMissingInterface(t *testing.T) {
        apfId := "APF_id_rApp_Kong_as_APF"
        apiName := "apiName"
 
        apfId := "APF_id_rApp_Kong_as_APF"
        apiName := "apiName"
 
@@ -330,12 +352,38 @@ func TestPublishUnpublishService(t *testing.T) {
        err = result.UnmarshalJsonToObject(&resultError)
        assert.NoError(t, err, "error unmarshaling response")
 
        err = result.UnmarshalJsonToObject(&resultError)
        assert.NoError(t, err, "error unmarshaling response")
 
-       assert.Contains(t, *resultError.Cause, "cannot read interfaceDescription")
+       assert.Contains(t, *resultError.Cause, "cannot read InterfaceDescriptions")
+}
+
+
+func TestPublishUnpublishWithoutVersionId(t *testing.T) {
+       apfId := "APF_id_rApp_Kong_as_APF"
+
+       myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
+       assert.Nil(t, err, "error reading env file")
+
+       testServiceIpv4 := common29122.Ipv4Addr(myEnv["TEST_SERVICE_IPV4"])
+       testServicePort := common29122.Port(myPorts["TEST_SERVICE_PORT"])
+
+       assert.NotEmpty(t, testServiceIpv4, "TEST_SERVICE_IPV4 is required in .env file for unit testing")
+       assert.NotZero(t, testServicePort, "TEST_SERVICE_PORT is required in .env file for unit testing")
+
+       apiVersion := "v1"
+       resourceName := "helloworld"
+       uri := "/helloworld"
+       apiName := "helloworld-v1"
+
+       aefId := "AEF_id_rApp_Kong_as_AEF"
+       namespace := "namespace"
+       repoName := "repoName"
+       chartName := "chartName"
+       releaseName := "releaseName"
+       description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
 
 
-       newServiceDescription = getServiceAPIDescription(aefId, apiName, description, testServiceIpv4, testServicePort)
+       newServiceDescription := getServiceAPIDescription(aefId, apiName, description, testServiceIpv4, testServicePort, apiVersion, resourceName, uri)
 
        // Publish a service for provider
 
        // Publish a service for provider
-       result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
+       result := testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
        assert.Equal(t, http.StatusCreated, result.Code())
 
        if result.Code() != http.StatusCreated {
        assert.Equal(t, http.StatusCreated, result.Code())
 
        if result.Code() != http.StatusCreated {
@@ -374,30 +422,184 @@ func TestPublishUnpublishService(t *testing.T) {
        assert.Equal(t, kongDataPlaneIPv4, resultServiceIpv4)
        assert.Equal(t, kongDataPlanePort, resultServicePort)
 
        assert.Equal(t, kongDataPlaneIPv4, resultServiceIpv4)
        assert.Equal(t, kongDataPlanePort, resultServicePort)
 
-       // Publish the same service again should result in Forbidden
-       newServiceDescription.ApiId = &newApiId
-       result = testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
-       assert.Equal(t, http.StatusForbidden, result.Code())
+       // Check Versions structure
+       version := aefProfile.Versions[0]
+       assert.Equal(t, "v1", version.ApiVersion)
 
 
-       err = result.UnmarshalBodyToObject(&resultError)
+       resource := (*version.Resources)[0]
+       communicationType := publishapi.CommunicationType("REQUEST_RESPONSE")
+       assert.Equal(t, communicationType, resource.CommType)
+
+       assert.Equal(t, 1, len(*resource.Operations))
+       var operation publishapi.Operation = "GET"
+       assert.Equal(t, operation, (*resource.Operations)[0])
+       assert.Equal(t, "helloworld", resource.ResourceName)
+       assert.Equal(t, "/helloworld-v1/port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/helloworld", resource.Uri)
+}
+
+func TestPublishUnpublishVersionId(t *testing.T) {
+       apfId := "APF_id_rApp_Kong_as_APF"
+
+       myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
+       assert.Nil(t, err, "error reading env file")
+
+       testServiceIpv4 := common29122.Ipv4Addr(myEnv["TEST_SERVICE_IPV4"])
+       testServicePort := common29122.Port(myPorts["TEST_SERVICE_PORT"])
+
+       assert.NotEmpty(t, testServiceIpv4, "TEST_SERVICE_IPV4 is required in .env file for unit testing")
+       assert.NotZero(t, testServicePort, "TEST_SERVICE_PORT is required in .env file for unit testing")
+
+       apiVersion := "v1"
+       resourceName := "helloworld-id"
+       uri := "~/helloworld/(?<helloworld-id>[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)"
+       apiName := "helloworld-v1-id"
+
+       aefId := "AEF_id_rApp_Kong_as_AEF"
+       namespace := "namespace"
+       repoName := "repoName"
+       chartName := "chartName"
+       releaseName := "releaseName"
+       description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
+
+       newServiceDescription := getServiceAPIDescription(aefId, apiName, description, testServiceIpv4, testServicePort, apiVersion, resourceName, uri)
+
+       // Publish a service for provider
+       result := testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
+       assert.Equal(t, http.StatusCreated, result.Code())
+
+       if result.Code() != http.StatusCreated {
+               log.Fatalf("failed to publish the service with HTTP result code %d", result.Code())
+               return
+       }
+
+       var resultService publishapi.ServiceAPIDescription
+       err = result.UnmarshalJsonToObject(&resultService)
        assert.NoError(t, err, "error unmarshaling response")
        assert.NoError(t, err, "error unmarshaling response")
-       assert.Contains(t, *resultError.Cause, "already published")
-       assert.Equal(t, http.StatusForbidden, *resultError.Status)
+       newApiId := "api_id_" + apiName
+       assert.Equal(t, newApiId, *resultService.ApiId)
 
 
-       // Delete the service
-       result = testutil.NewRequest().Delete("/published-apis/v1/"+apfId+"/service-apis/"+newApiId).Go(t, eServiceManager)
-       assert.Equal(t, http.StatusNoContent, result.Code())
+       assert.Equal(t, "http://example.com/published-apis/v1/"+apfId+"/service-apis/"+*resultService.ApiId, result.Recorder.Header().Get(echo.HeaderLocation))
 
 
-       // Check no services published
-       result = testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis").Go(t, eServiceManager)
+       // Check that the service is published for the provider
+       result = testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis/"+newApiId).Go(t, eServiceManager)
        assert.Equal(t, http.StatusOK, result.Code())
 
        assert.Equal(t, http.StatusOK, result.Code())
 
-       // Parse JSON from the response body
-       err = result.UnmarshalJsonToObject(&resultServices)
+       err = result.UnmarshalJsonToObject(&resultService)
        assert.NoError(t, err, "error unmarshaling response")
        assert.NoError(t, err, "error unmarshaling response")
+       assert.Equal(t, newApiId, *resultService.ApiId)
+
+       aefProfile := (*resultService.AefProfiles)[0]
+       interfaceDescription := (*aefProfile.InterfaceDescriptions)[0]
+
+       resultServiceIpv4 := *interfaceDescription.Ipv4Addr
+       resultServicePort := *interfaceDescription.Port
+
+       kongDataPlaneIPv4 := common29122.Ipv4Addr(myEnv["KONG_DATA_PLANE_IPV4"])
+       kongDataPlanePort := common29122.Port(myPorts["KONG_DATA_PLANE_PORT"])
+
+       assert.NotEmpty(t, kongDataPlaneIPv4, "KONG_DATA_PLANE_IPV4 is required in .env file for unit testing")
+       assert.NotZero(t, kongDataPlanePort, "KONG_DATA_PLANE_PORT is required in .env file for unit testing")
+
+       assert.Equal(t, kongDataPlaneIPv4, resultServiceIpv4)
+       assert.Equal(t, kongDataPlanePort, resultServicePort)
+
+       // Check Versions structure
+       version := aefProfile.Versions[0]
+       assert.Equal(t, "v1", version.ApiVersion)
+
+       resource := (*version.Resources)[0]
+       communicationType := publishapi.CommunicationType("REQUEST_RESPONSE")
+       assert.Equal(t, communicationType, resource.CommType)
+
+       assert.Equal(t, 1, len(*resource.Operations))
+       var operation publishapi.Operation = "GET"
+       assert.Equal(t, operation, (*resource.Operations)[0])
+
+       assert.Equal(t, "helloworld-id", resource.ResourceName)
+       assert.Equal(t, "~/helloworld-v1-id/port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/helloworld/v1/(?<helloworld-id>[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)", resource.Uri)
+}
+
+func TestPublishUnpublishServiceNoVersionWithId(t *testing.T) {
+       apfId := "APF_id_rApp_Kong_as_APF"
+
+       myEnv, myPorts, err := mockConfigReader.ReadDotEnv()
+       assert.Nil(t, err, "error reading env file")
+
+       testServiceIpv4 := common29122.Ipv4Addr(myEnv["TEST_SERVICE_IPV4"])
+       testServicePort := common29122.Port(myPorts["TEST_SERVICE_PORT"])
+
+       assert.NotEmpty(t, testServiceIpv4, "TEST_SERVICE_IPV4 is required in .env file for unit testing")
+       assert.NotZero(t, testServicePort, "TEST_SERVICE_PORT is required in .env file for unit testing")
+
+       apiVersion := ""
+       resourceName := "helloworld-no-version"
+       uri := "~/helloworld/(?<helloworld-id>[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)"
+       apiName := "helloworld-no-version"
+
+       aefId := "AEF_id_rApp_Kong_as_AEF"
+       namespace := "namespace"
+       repoName := "repoName"
+       chartName := "chartName"
+       releaseName := "releaseName"
+       description := fmt.Sprintf("Description,%s,%s,%s,%s", namespace, repoName, chartName, releaseName)
+
+       newServiceDescription := getServiceAPIDescription(aefId, apiName, description, testServiceIpv4, testServicePort, apiVersion, resourceName, uri)
+
+       // Publish a service for provider
+       result := testutil.NewRequest().Post("/published-apis/v1/"+apfId+"/service-apis").WithJsonBody(newServiceDescription).Go(t, eServiceManager)
+       assert.Equal(t, http.StatusCreated, result.Code())
+
+       if result.Code() != http.StatusCreated {
+               log.Fatalf("failed to publish the service with HTTP result code %d", result.Code())
+               return
+       }
+
+       var resultService publishapi.ServiceAPIDescription
+       err = result.UnmarshalJsonToObject(&resultService)
+       assert.NoError(t, err, "error unmarshaling response")
+       newApiId := "api_id_" + apiName
+       assert.Equal(t, newApiId, *resultService.ApiId)
+
+       assert.Equal(t, "http://example.com/published-apis/v1/"+apfId+"/service-apis/"+*resultService.ApiId, result.Recorder.Header().Get(echo.HeaderLocation))
+
+       // Check that the service is published for the provider
+       result = testutil.NewRequest().Get("/published-apis/v1/"+apfId+"/service-apis/"+newApiId).Go(t, eServiceManager)
+       assert.Equal(t, http.StatusOK, result.Code())
+
+       err = result.UnmarshalJsonToObject(&resultService)
+       assert.NoError(t, err, "error unmarshaling response")
+       assert.Equal(t, newApiId, *resultService.ApiId)
+
+       aefProfile := (*resultService.AefProfiles)[0]
+       interfaceDescription := (*aefProfile.InterfaceDescriptions)[0]
+
+       resultServiceIpv4 := *interfaceDescription.Ipv4Addr
+       resultServicePort := *interfaceDescription.Port
+
+       kongDataPlaneIPv4 := common29122.Ipv4Addr(myEnv["KONG_DATA_PLANE_IPV4"])
+       kongDataPlanePort := common29122.Port(myPorts["KONG_DATA_PLANE_PORT"])
+
+       assert.NotEmpty(t, kongDataPlaneIPv4, "KONG_DATA_PLANE_IPV4 is required in .env file for unit testing")
+       assert.NotZero(t, kongDataPlanePort, "KONG_DATA_PLANE_PORT is required in .env file for unit testing")
+
+       assert.Equal(t, kongDataPlaneIPv4, resultServiceIpv4)
+       assert.Equal(t, kongDataPlanePort, resultServicePort)
+
+       // Check Versions structure
+       version := aefProfile.Versions[0]
+       assert.Equal(t, "", version.ApiVersion)
+
+       resource := (*version.Resources)[0]
+       communicationType := publishapi.CommunicationType("REQUEST_RESPONSE")
+       assert.Equal(t, communicationType, resource.CommType)
+
+       assert.Equal(t, 1, len(*resource.Operations))
+       var operation publishapi.Operation = "GET"
+       assert.Equal(t, operation, (*resource.Operations)[0])
+
+       assert.Equal(t, "helloworld-no-version", resource.ResourceName)
+       assert.Equal(t, "~/helloworld-no-version/port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/helloworld/(?<helloworld-id>[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)", resource.Uri)
 
 
-       // Check if the parsed array is empty
-       assert.Zero(t, len(resultServices))
        capifCleanUp()
 }
 
        capifCleanUp()
 }
 
@@ -449,7 +651,16 @@ func registerHandlers(e *echo.Echo, myEnv map[string]string, myPorts map[string]
        return err
 }
 
        return err
 }
 
-func getServiceAPIDescription(aefId, apiName, description string, testServiceIpv4 common29122.Ipv4Addr, testServicePort common29122.Port) publishapi.ServiceAPIDescription {
+func getServiceAPIDescription(
+       aefId string,
+       apiName string,
+       description string,
+       testServiceIpv4 common29122.Ipv4Addr,
+       testServicePort common29122.Port,
+       apiVersion string,
+       resourceName string,
+       uri string) publishapi.ServiceAPIDescription {
+
        domainName := "Kong"
        var protocol publishapi.Protocol = "HTTP_1_1"
 
        domainName := "Kong"
        var protocol publishapi.Protocol = "HTTP_1_1"
 
@@ -470,15 +681,15 @@ func getServiceAPIDescription(aefId, apiName, description string, testServiceIpv
                                Protocol:   &protocol,
                                Versions: []publishapi.Version{
                                        {
                                Protocol:   &protocol,
                                Versions: []publishapi.Version{
                                        {
-                                               ApiVersion: "v1",
+                                               ApiVersion: apiVersion,
                                                Resources: &[]publishapi.Resource{
                                                        {
                                                                CommType: "REQUEST_RESPONSE",
                                                                Operations: &[]publishapi.Operation{
                                                                        "GET",
                                                                },
                                                Resources: &[]publishapi.Resource{
                                                        {
                                                                CommType: "REQUEST_RESPONSE",
                                                                Operations: &[]publishapi.Operation{
                                                                        "GET",
                                                                },
-                                                               ResourceName: "helloworld",
-                                                               Uri:          "/helloworld",
+                                                               ResourceName: resourceName,
+                                                               Uri:          uri,
                                                        },
                                                },
                                        },
                                                        },
                                                },
                                        },
index d7246bd..ac84c0d 100644 (file)
@@ -25,9 +25,12 @@ import (
        "fmt"
        "net/http"
        "net/url"
        "fmt"
        "net/http"
        "net/url"
+       "regexp"
+       "strconv"
        "strings"
 
        resty "github.com/go-resty/resty/v2"
        "strings"
 
        resty "github.com/go-resty/resty/v2"
+       "github.com/google/uuid"
        log "github.com/sirupsen/logrus"
 
        common29122 "oransc.org/nonrtric/servicemanager/internal/common29122"
        log "github.com/sirupsen/logrus"
 
        common29122 "oransc.org/nonrtric/servicemanager/internal/common29122"
@@ -56,7 +59,7 @@ func (sd *ServiceAPIDescription) RegisterKong(
        )
        kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongControlPlaneIPv4, kongControlPlanePort)
 
        )
        kongControlPlaneURL := fmt.Sprintf("%s://%s:%d", kongProtocol, kongControlPlaneIPv4, kongControlPlanePort)
 
-       statusCode, err = sd.createKongRoutes(kongControlPlaneURL, apfId)
+       statusCode, err = sd.createKongInterfaceDescriptions(kongControlPlaneURL, apfId)
        if (err != nil) || (statusCode != http.StatusCreated) {
                return statusCode, err
        }
        if (err != nil) || (statusCode != http.StatusCreated) {
                return statusCode, err
        }
@@ -67,109 +70,338 @@ func (sd *ServiceAPIDescription) RegisterKong(
        return statusCode, nil
 }
 
        return statusCode, nil
 }
 
-func (sd *ServiceAPIDescription) createKongRoutes(kongControlPlaneURL string, apfId string) (int, error) {
-       log.Trace("entering createKongRoutes")
+func (sd *ServiceAPIDescription) createKongInterfaceDescriptions(kongControlPlaneURL string, apfId string) (int, error) {
+       log.Trace("entering createKongInterfaceDescriptions")
+
        var (
                statusCode int
                err        error
        )
        var (
                statusCode int
                err        error
        )
-
        client := resty.New()
        client := resty.New()
+       outputUris := []string{}
+
+       if sd == nil  {
+               err = errors.New("cannot read ServiceAPIDescription")
+               log.Errorf(err.Error())
+               return http.StatusBadRequest, err
+       }
+
+       if (sd.AefProfiles == nil) || (len(*sd.AefProfiles) < 1) {
+               err = errors.New("cannot read AefProfiles")
+               log.Errorf(err.Error())
+               return http.StatusBadRequest, err
+       }
 
        profiles := *sd.AefProfiles
        for _, profile := range profiles {
 
        profiles := *sd.AefProfiles
        for _, profile := range profiles {
-               log.Debugf("createKongRoutes, AefId %s", profile.AefId)
+               log.Debugf("createKongInterfaceDescriptions, AefId %s", profile.AefId)
+
+               if (profile.Versions == nil) || (len(profile.Versions) < 1) {
+                       err := errors.New("cannot read Versions")
+                       log.Errorf(err.Error())
+                       return http.StatusBadRequest, err
+               }
+
                for _, version := range profile.Versions {
                for _, version := range profile.Versions {
-                       log.Debugf("createKongRoutes, apiVersion \"%s\"", version.ApiVersion)
-                       for _, resource := range *version.Resources {
-                               statusCode, err = sd.createKongRoute(kongControlPlaneURL, client, resource, apfId, profile.AefId, version.ApiVersion)
-                               if (err != nil) || (statusCode != http.StatusCreated) {
-                                       return statusCode, err
+                       log.Debugf("createKongInterfaceDescriptions, apiVersion \"%s\"", version.ApiVersion)
+
+                       if (profile.InterfaceDescriptions == nil) || (len(*profile.InterfaceDescriptions) < 1) {
+                               err := errors.New("cannot read InterfaceDescriptions")
+                               log.Errorf(err.Error())
+                               return http.StatusBadRequest, err
+                       }
+
+                       for _, interfaceDescription := range *profile.InterfaceDescriptions {
+                               log.Debugf("createKongInterfaceDescriptions, Ipv4Addr %s", *interfaceDescription.Ipv4Addr)
+                               log.Debugf("createKongInterfaceDescriptions, Port %d", *interfaceDescription.Port)
+                               if uint(*interfaceDescription.Port) > 65535 {
+                                       err := errors.New("invalid Port")
+                                       log.Errorf(err.Error())
+                                       return http.StatusBadRequest, err
+                               }
+
+                               if interfaceDescription.SecurityMethods == nil {
+                                       err := errors.New("cannot read SecurityMethods")
+                                       log.Errorf(err.Error())
+                                       return http.StatusBadRequest, err
+                               }
+
+                               for _, securityMethod := range *interfaceDescription.SecurityMethods {
+                                       log.Debugf("createKongInterfaceDescriptions, SecurityMethod %s", securityMethod)
+
+                                       if (securityMethod != SecurityMethodOAUTH) && (securityMethod != SecurityMethodPKI) && (securityMethod != SecurityMethodPSK) {
+                                               msg := fmt.Sprintf("invalid SecurityMethod %s", securityMethod)
+                                               err := errors.New(msg)
+                                               log.Errorf(err.Error())
+                                               return http.StatusBadRequest, err
+                                       }
+                               }
+
+                               if (version.Resources == nil) || (len(*version.Resources) < 1) {
+                                       err := errors.New("cannot read Resources")
+                                       log.Errorf(err.Error())
+                                       return http.StatusBadRequest, err
+                               }
+
+                               for _, resource := range *version.Resources {
+                                       var specUri string
+                                       specUri, statusCode, err = sd.createKongServiceRoutePrecheck(kongControlPlaneURL, client, interfaceDescription, resource, apfId, profile.AefId, version.ApiVersion)
+                                       if (err != nil) || (statusCode != http.StatusCreated) {
+                                               return statusCode, err
+                                       }
+                                       log.Debugf("createKongInterfaceDescriptions, specUri %s", specUri)
+                                       outputUris = append(outputUris, specUri)
+                                       log.Tracef("createKongInterfaceDescriptions, len(outputUris) %d", len(outputUris))
+                                       log.Tracef("createKongInterfaceDescriptions, outputUris %v", outputUris)
+                               }
+                       }
+               }
+       }
+
+       // Our list of returned resources has the new resource with the hash code and version number
+       m := 0
+       for i, profile := range profiles {
+               for j, version := range profile.Versions {
+                       var newResources []Resource
+                       for range *profile.InterfaceDescriptions {
+                               log.Tracef("createKongInterfaceDescriptions, range over *profile.InterfaceDescriptions")
+                               for _, resource := range *version.Resources {
+                                       log.Tracef("createKongInterfaceDescriptions, m %d outputUris[m] %s", m, outputUris[m])
+                                       resource.Uri = outputUris[m]
+                                       m = m + 1
+                                       // Build a new list of resources with updated uris
+                                       newResources = append(newResources, resource)
+                                       log.Tracef("createKongInterfaceDescriptions, newResources %v", newResources)
                                }
                        }
                                }
                        }
+                       // Swap over to the new list of uris
+                       *profiles[i].Versions[j].Resources = newResources
+                       log.Tracef("createKongInterfaceDescriptions, assigned *profiles[i].Versions[j].Resources %v", *profiles[i].Versions[j].Resources)
                }
        }
                }
        }
+       log.Tracef("exiting createKongInterfaceDescriptions statusCode %d", statusCode)
+
        return statusCode, nil
 }
 
        return statusCode, nil
 }
 
-func (sd *ServiceAPIDescription) createKongRoute(
+func (sd *ServiceAPIDescription) createKongServiceRoutePrecheck(
                kongControlPlaneURL string,
                client *resty.Client,
                kongControlPlaneURL string,
                client *resty.Client,
+               interfaceDescription InterfaceDescription,
                resource Resource,
                apfId string,
                aefId string,
                resource Resource,
                apfId string,
                aefId string,
-               apiVersion string ) (int, error) {
-       log.Trace("entering createKongRoute")
+               apiVersion string ) (string, int, error) {
+       log.Trace("entering createKongServiceRoutePrecheck")
+       log.Debugf("createKongServiceRoutePrecheck, aefId %s", aefId)
 
 
-       resourceName := resource.ResourceName
-       apiId := *sd.ApiId
+       if (resource.Operations == nil) || (len(*resource.Operations) < 1) {
+               err := errors.New("cannot read Resource.Operations")
+               log.Errorf(err.Error())
+               return "", http.StatusBadRequest, err
+       }
 
 
-       tags := buildTags(apfId, aefId, apiId, apiVersion, resourceName)
-       log.Debugf("createKongRoute, tags %s", tags)
+       log.Debugf("createKongServiceRoutePrecheck, resource.Uri %s", resource.Uri)
+       if resource.Uri == "" {
+               err := errors.New("cannot read Resource.Uri")
+               log.Errorf(err.Error())
+               return "", http.StatusBadRequest, err
+       }
 
 
-       serviceName := apiId + "_" + resourceName
-       routeName := serviceName
+       log.Debugf("createKongServiceRoutePrecheck, ResourceName %v", resource.ResourceName)
 
 
-       log.Debugf("createKongRoute, serviceName %s", serviceName)
-       log.Debugf("createKongRoute, routeName %s", routeName)
-       log.Debugf("createKongRoute, aefId %s", aefId)
+       if resource.ResourceName == "" {
+               err := errors.New("cannot read Resource.ResourceName")
+               log.Errorf(err.Error())
+               return "", http.StatusBadRequest, err
+       }
 
 
-       uri := buildUriWithVersion(apiVersion, resource.Uri)
-       log.Debugf("createKongRoute, uri %s", uri)
+       if (resource.CommType != CommunicationTypeREQUESTRESPONSE) && (resource.CommType != CommunicationTypeSUBSCRIBENOTIFY) {
+               err := errors.New("invalid Resource.CommType")
+               log.Errorf(err.Error())
+               return "", http.StatusBadRequest, err
+       }
 
 
-       statusCode, err := sd.createKongService(kongControlPlaneURL, serviceName, uri, tags)
-       if (err != nil) || (statusCode != http.StatusCreated) {
-               return statusCode, err
+       specUri := resource.Uri
+       kongRegexUri, _ := deriveKongPattern(resource.Uri)
+
+       specUri, statusCode, err := sd.createKongServiceRoute(kongControlPlaneURL, client, interfaceDescription, kongRegexUri, specUri, apfId, aefId, apiVersion, resource)
+       if (err != nil) || ((statusCode != http.StatusCreated) ) {
+               // We carry on if we tried to create a duplicate service. We depend on Kong route matching.
+               return specUri, statusCode, err
        }
 
        }
 
-       kongRoutesURL := kongControlPlaneURL + "/services/" + serviceName + "/routes"
+       return specUri, statusCode, err
+}
+
+func insertVersion(version string, route string) string {
+       versionedRoute := route
+
+       if version != "" {
+               sep := "/"
+               n := 3
 
 
-       // Define the route information for Kong
-       kongRouteInfo := map[string]interface{}{
-               "name":       routeName,
-               "paths":      []string{uri},
-               "methods":    resource.Operations,
-               "tags":       tags,
-               "strip_path": true,
+               foundRegEx := false
+               if strings.HasPrefix(route, "~") {
+                       log.Debug("insertVersion, found regex prefix")
+                       foundRegEx = true
+                       route = strings.TrimPrefix(route, "~")
+               }
+
+               log.Debugf("insertVersion route %s", route)
+               split := strings.SplitAfterN(route, sep, n)
+               log.Debugf("insertVersion split %q", split)
+
+               versionedRoute = split[0]
+               if len(split) == 2 {
+                       versionedRoute = split[0] + split[1]
+               } else if len(split) > 2 {
+                       versionedRoute = split[0] + split[1] + version + sep + split[2]
+               }
+
+               if foundRegEx {
+                       versionedRoute = "~" + versionedRoute
+               }
+       }
+       log.Debugf("insertVersion versionedRoute %s", versionedRoute)
+
+       return versionedRoute
+}
+
+func (sd *ServiceAPIDescription) createKongServiceRoute(
+               kongControlPlaneURL string,
+               client *resty.Client,
+               interfaceDescription InterfaceDescription,
+               kongRegexUri string,
+               specUri string,
+               apfId string,
+               aefId string,
+               apiVersion string,
+               resource Resource) (string, int, error) {
+       log.Tracef("entering createKongServiceRoute")
+
+       var (
+               statusCode int
+               err error
+       )
+
+       kongControlPlaneURLParsed, err := url.Parse(kongControlPlaneURL)
+       if err != nil {
+               return "", http.StatusInternalServerError, err
+       }
+       log.Debugf("createKongServiceRoute, kongControlPlaneURL %s", kongControlPlaneURL)
+       log.Debugf("createKongServiceRoute, kongControlPlaneURLParsed.Scheme %s", kongControlPlaneURLParsed.Scheme)
+
+       log.Debugf("createKongServiceRoute, kongRegexUri %s", kongRegexUri)
+       log.Debugf("createKongServiceRoute, specUri %s", specUri)
+
+       kongRegexUri = insertVersion(apiVersion, kongRegexUri)
+       kongServiceUri := kongRegexUri
+       log.Debugf("createKongServiceRoute, kongServiceUri after insertVersion, %s", kongServiceUri)
+
+       specUri = insertVersion(apiVersion, specUri)
+       log.Debugf("createKongServiceRoute, specUri after insertVersion, %s", specUri)
+
+       if strings.HasPrefix(kongServiceUri, "~") {
+               log.Debug("createKongServiceRoute, found regex prefix")
+
+               // For our Kong Service path, we omit the leading ~ and take the path up to the regex, not including the '('
+               kongServiceUri = kongServiceUri[1:]
+               index := strings.Index(kongServiceUri, "(?")
+               if (index != -1 ) {
+                       kongServiceUri = kongServiceUri[:index]
+               } else {
+                       log.Errorf("createKongServiceRoute, regex characters '(?' not found in the regex %s", kongServiceUri)
+                       return "", http.StatusBadRequest, err
+               }
+       } else {
+               log.Debug("createKongServiceRoute, no regex prefix found")
+       }
+       log.Debugf("createKongServiceRoute, kongServiceUri, path up to regex %s", kongServiceUri)
+
+       ipv4Addr := *interfaceDescription.Ipv4Addr
+       port := *interfaceDescription.Port
+
+       portAsInt := int(port)
+       interfaceDescriptionSeed := string(ipv4Addr) + strconv.Itoa(portAsInt)
+       interfaceDescUuid := uuid.NewSHA1(uuid.NameSpaceURL, []byte(interfaceDescriptionSeed))
+       uriPrefix := "port-" + strconv.Itoa(portAsInt) + "-hash-" + interfaceDescUuid.String()
+
+       resourceName := resource.ResourceName
+
+       apiId := *sd.ApiId
+       kongServiceName := apiId + "-" + resourceName
+       kongServiceNamePrefix := kongServiceName + "-" + uriPrefix
+
+       log.Debugf("createKongServiceRoute, kongServiceName %s", kongServiceName)
+       log.Debugf("createKongServiceRoute, kongServiceNamePrefix %s", kongServiceNamePrefix)
+
+       tags := buildTags(apfId, aefId, apiId, apiVersion, resourceName)
+       log.Debugf("createKongServiceRoute, tags %s", tags)
+
+       kongServiceInfo := map[string]interface{}{
+               "host":     ipv4Addr,
+               "name":     kongServiceNamePrefix,
+               "port":     port,
+               "protocol": kongControlPlaneURLParsed.Scheme,
+               "path":     kongServiceUri,
+               "tags":     tags,
        }
 
        }
 
+       // Kong admin API endpoint for creating a service
+       kongServicesURL := kongControlPlaneURL + "/services"
+
        // Make the POST request to create the Kong service
        resp, err := client.R().
                SetHeader("Content-Type", "application/json").
        // Make the POST request to create the Kong service
        resp, err := client.R().
                SetHeader("Content-Type", "application/json").
-               SetBody(kongRouteInfo).
-               Post(kongRoutesURL)
+               SetBody(kongServiceInfo).
+               Post(kongServicesURL)
 
        // Check for errors in the request
        if err != nil {
 
        // Check for errors in the request
        if err != nil {
-               log.Debugf("createKongRoute POST Error: %v", err)
-               return resp.StatusCode(), err
+               log.Errorf("createKongServiceRoute, Request Error: %v", err)
+               return "", http.StatusInternalServerError, err
        }
 
        // Check the response status code
        }
 
        // Check the response status code
-       if resp.StatusCode() == http.StatusCreated {
-               log.Infof("kong route %s created successfully", routeName)
+       statusCode = resp.StatusCode()
+       if statusCode == http.StatusCreated {
+               log.Infof("kong service %s created successfully", kongServiceNamePrefix)
+       } else if resp.StatusCode() == http.StatusConflict {
+               log.Errorf("kong service already exists. Status code: %d", resp.StatusCode())
+               err = fmt.Errorf("service with identical apiName is already published") // for compatibilty with Capif error message on a duplicate service
+               statusCode = http.StatusForbidden                                       // for compatibilty with the spec, TS29222_CAPIF_Publish_Service_API
        } else {
        } else {
-               log.Debugf("kongRoutesURL %s", kongRoutesURL)
-               err = fmt.Errorf("error creating Kong route. Status code: %d", resp.StatusCode())
-               log.Error(err.Error())
+               err = fmt.Errorf("error creating Kong service. Status code: %d", resp.StatusCode())
+       }
+       if err != nil {
+               log.Errorf(err.Error())
                log.Errorf("response body: %s", resp.Body())
                log.Errorf("response body: %s", resp.Body())
-               return resp.StatusCode(), err
+               return "", statusCode, err
        }
 
        }
 
-       return resp.StatusCode(), nil
-}
+       // Create matching route
+       routeName := kongServiceNamePrefix
 
 
-func buildUriWithVersion(apiVersion string, uri string) string {
-       if apiVersion != "" {
-               if apiVersion[0] != '/' {
-                       apiVersion = "/" + apiVersion
-               }
-               if apiVersion[len(apiVersion)-1] != '/' && uri[0] != '/' {
-                       apiVersion = apiVersion + "/"
-               }
-               uri = apiVersion + uri
+       kongRouteUri := prependUri(uriPrefix, kongRegexUri)
+       log.Debugf("createKongServiceRoute, kongRouteUri with uriPrefix %s", kongRouteUri)
+
+       kongRouteUri = prependUri(sd.ApiName, kongRouteUri)
+       log.Debugf("createKongServiceRoute, kongRouteUri with apiName %s", kongRouteUri)
+
+       specUri = prependUri(uriPrefix, specUri)
+       log.Debugf("createKongServiceRoute, specUri with uriPrefix %s", specUri)
+
+       specUri = prependUri(sd.ApiName, specUri)
+       log.Debugf("createKongServiceRoute, specUri with apiName %s", specUri)
+
+       statusCode, err = sd.createRouteForService(kongControlPlaneURL, client, resource, routeName, kongRouteUri, kongRegexUri, tags)
+       if err != nil {
+               log.Errorf(err.Error())
+               return kongRouteUri, statusCode, err
        }
        }
-       return uri
+
+       return specUri, statusCode, err
 }
 
 func buildTags(apfId string, aefId string, apiId string, apiVersion string, resourceName string) []string  {
 }
 
 func buildTags(apfId string, aefId string, apiId string, apiVersion string, resourceName string) []string  {
@@ -191,88 +423,223 @@ func buildTags(apfId string, aefId string, apiId string, apiVersion string, reso
        return tagsSlice
 }
 
        return tagsSlice
 }
 
-func (sd *ServiceAPIDescription) createKongService(kongControlPlaneURL string, kongServiceName string, kongServiceUri string, tags []string) (int, error) {
-       log.Tracef("entering createKongService")
-       log.Tracef("createKongService, kongServiceName %s", kongServiceName)
+func prependUri(prependUri string, uri string) string {
+       if prependUri != "" {
+               trimmedUri := uri
+               foundRegEx := false
+               if strings.HasPrefix(uri, "~") {
+                       log.Debug("prependUri, found regex prefix")
+                       foundRegEx = true
+                       trimmedUri = strings.TrimPrefix(uri, "~")
+                       log.Debugf("prependUri, TrimPrefix trimmedUri %s", trimmedUri)
+               }
 
 
-       // Define the service information for Kong
-       firstAEFProfileIpv4Addr, firstAEFProfilePort, err := sd.findFirstAEFProfile()
-       if err != nil {
-               return http.StatusBadRequest, err
+               if prependUri[0] != '/' {
+                       prependUri = "/" + prependUri
+               }
+               if prependUri[len(prependUri)-1] != '/' && trimmedUri[0] != '/' {
+                       prependUri = prependUri + "/"
+               }
+               uri = prependUri + trimmedUri
+               if foundRegEx {
+                       uri = "~" + uri
+               }
        }
        }
+       return uri
+}
 
 
-       kongControlPlaneURLParsed, err := url.Parse(kongControlPlaneURL)
-       if err != nil {
-               return http.StatusInternalServerError, err
+func (sd *ServiceAPIDescription) createRouteForService(
+               kongControlPlaneURL string,
+               client *resty.Client,
+               resource Resource,
+               routeName string,
+               kongRouteUri string,
+               kongRegexUri string,
+               tags []string) (int, error)  {
+
+       log.Debugf("createRouteForService, kongRouteUri %s", kongRouteUri)
+
+       // Create a url.Values map to hold the form data
+       data := url.Values{}
+       data.Set("strip_path", "true")
+       log.Debugf("createRouteForService, strip_path %s", data.Get("strip_path"))
+       data.Set("name", routeName)
+
+       routeUriPaths := []string{kongRouteUri}
+       for _, path := range routeUriPaths {
+               log.Debugf("createRouteForService, path %s", path)
+               data.Add("paths", path)
        }
        }
-       log.Debugf("kongControlPlaneURL %s", kongControlPlaneURL)
-       log.Debugf("kongControlPlaneURLParsed.Scheme %s", kongControlPlaneURLParsed.Scheme)
 
 
-       kongServiceInfo := map[string]interface{}{
-               "host":     firstAEFProfileIpv4Addr,
-               "name":     kongServiceName,
-               "port":     firstAEFProfilePort,
-               "protocol": kongControlPlaneURLParsed.Scheme,
-               "path":     kongServiceUri,
-               "tags":     tags,
+       for _, tag := range tags {
+               log.Debugf("createRouteForService, tag %s", tag)
+               data.Add("tags", tag)
        }
 
        }
 
-       // Kong admin API endpoint for creating a service
-       kongServicesURL := kongControlPlaneURL + "/services"
+       for _, op := range *resource.Operations {
+               log.Debugf("createRouteForService, op %s", string(op))
+               data.Add("methods", string(op))
+       }
 
 
-       // Create a new Resty client
-       client := resty.New()
+       // Encode the data to application/x-www-form-urlencoded format
+       encodedData := data.Encode()
 
        // Make the POST request to create the Kong service
 
        // Make the POST request to create the Kong service
+       serviceName := routeName
+       kongRoutesURL := kongControlPlaneURL + "/services/" + serviceName + "/routes"
        resp, err := client.R().
        resp, err := client.R().
-               SetHeader("Content-Type", "application/json").
-               SetBody(kongServiceInfo).
-               Post(kongServicesURL)
+               SetHeader("Content-Type", "application/x-www-form-urlencoded").
+               SetBody(strings.NewReader(encodedData)).
+               Post(kongRoutesURL)
 
        // Check for errors in the request
        if err != nil {
 
        // Check for errors in the request
        if err != nil {
-               log.Errorf("create Kong Service Request Error: %v", err)
-               return http.StatusInternalServerError, err
+               log.Debugf("createRouteForService POST Error: %v", err)
+               return resp.StatusCode(), err
        }
 
        // Check the response status code
        }
 
        // Check the response status code
-       statusCode := resp.StatusCode()
-       if statusCode == http.StatusCreated {
-               log.Infof("kong service %s created successfully", kongServiceName)
-       } else if resp.StatusCode() == http.StatusConflict {
-               log.Errorf("kong service already exists. Status code: %d", resp.StatusCode())
-               err = fmt.Errorf("service with identical apiName is already published") // for compatibilty with Capif error message on a duplicate service
-               statusCode = http.StatusForbidden                                       // for compatibilty with the spec, TS29222_CAPIF_Publish_Service_API
+       if resp.StatusCode() == http.StatusCreated {
+               log.Infof("kong route %s created successfully", routeName)
+
+               index := strings.Index(kongRegexUri, "(?")
+               if index != -1 {
+                       log.Debugf("createRouteForService, found regex in %s", kongRegexUri)
+                       requestTransformerUri := strings.TrimPrefix(kongRegexUri, "~")
+                       log.Debugf("createRouteForService, requestTransformerUri %s", requestTransformerUri)
+
+                       statusCode, err := sd.createRequestTransformer(kongControlPlaneURL, client, routeName, requestTransformerUri)
+                       if (err != nil) || ((statusCode != http.StatusCreated) && (statusCode != http.StatusForbidden)) {
+                               return statusCode, err
+                       }
+               } else {
+                       log.Debug("createRouteForService, no variable name found")
+               }
        } else {
        } else {
-               err = fmt.Errorf("error creating Kong service. Status code: %d", resp.StatusCode())
+               log.Debugf("kongRoutesURL %s", kongRoutesURL)
+               err = fmt.Errorf("error creating Kong route. Status code: %d", resp.StatusCode())
+               log.Error(err.Error())
+               log.Errorf("response body: %s", resp.Body())
+               return resp.StatusCode(), err
+       }
+
+       return resp.StatusCode(), nil
+}
+
+func (sd *ServiceAPIDescription) createRequestTransformer(
+       kongControlPlaneURL string,
+       client *resty.Client,
+       routeName string,
+       routePattern string) (int, error) {
+
+       log.Trace("entering createRequestTransformer")
+
+       // Make the POST request to create the Kong Request Transformer
+       kongRequestTransformerURL := kongControlPlaneURL + "/routes/" + routeName + "/plugins"
+
+       transformPattern, _ := deriveTransformPattern(routePattern)
+
+       // Create the form data
+       formData := url.Values{
+               "name":                  {"request-transformer"},
+               "config.replace.uri":    {transformPattern},
        }
        }
+       encodedData := formData.Encode()
+
+       // Create a new HTTP POST request
+       resp, err := client.R().
+               SetHeader("Content-Type", "application/x-www-form-urlencoded").
+               SetBody(strings.NewReader(encodedData)).
+               Post(kongRequestTransformerURL)
+
+       // Check for errors in the request
        if err != nil {
        if err != nil {
-               log.Errorf(err.Error())
+               log.Debugf("createRequestTransformer POST Error: %v", err)
+               return resp.StatusCode(), err
+       }
+
+       // Check the response status code
+       if resp.StatusCode() == http.StatusCreated {
+               log.Infof("kong request transformer for route %s created successfully", routeName)
+       } else {
+               log.Debugf("kongRequestTransformerURL %s", kongRequestTransformerURL)
+               err = fmt.Errorf("error creating Kong request transformer. Status code: %d", resp.StatusCode())
+               log.Error(err.Error())
                log.Errorf("response body: %s", resp.Body())
                log.Errorf("response body: %s", resp.Body())
+               return resp.StatusCode(), err
        }
 
        }
 
-       return statusCode, err
+       return resp.StatusCode(), nil
 }
 
 }
 
-func (sd *ServiceAPIDescription) findFirstAEFProfile() (common29122.Ipv4Addr, common29122.Port, error) {
-       log.Tracef("entering findFirstAEFProfile")
-       var aefProfile AefProfile
-       if *sd.AefProfiles != nil {
-               aefProfile = (*sd.AefProfiles)[0]
+// Function to derive the transform pattern from the route pattern
+func deriveKongPattern(routePattern string) (string, error) {
+       log.Trace("entering deriveKongPattern")
+       log.Debugf("deriveKongPattern routePattern %s", routePattern)
+
+       // Regular expression to match variable names
+       re := regexp.MustCompile(`\{([a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)\}`)
+       log.Debugf("deriveKongPattern MustCompile %v", re)
+
+       // Find all matches in the route pattern
+       matches := re.FindAllStringSubmatch(routePattern, -1)
+       log.Debugf("deriveKongPattern FindAllStringSubmatch %v", re)
+
+       transformPattern := routePattern
+       for _, match := range matches {
+               // match[0] is the full match with braces
+               // match[1] is the uri variable name
+               log.Debugf("deriveKongPattern match %v", match)
+               log.Debugf("deriveKongPattern match[0] %v", match[0])
+               log.Debugf("deriveKongPattern match[1] %v", match[1])
+               placeholder := fmt.Sprintf("(?<%s>[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*)", match[1])
+               // Replace the variable with the Kong regex placeholder
+               transformPattern = strings.Replace(transformPattern, match[0], placeholder, 1)
        }
        }
-       if (*sd.AefProfiles == nil) || (aefProfile.InterfaceDescriptions == nil) {
-               err := errors.New("cannot read interfaceDescription")
-               log.Errorf(err.Error())
-               return "", common29122.Port(0), err
+       log.Debugf("deriveKongPattern transformPattern %s", transformPattern)
+
+       if len(matches) != 0 {
+               transformPattern = "~" + transformPattern
+               log.Debugf("deriveKongPattern transformPattern with prefix %s", transformPattern)
+       }
+
+       return transformPattern, nil
+}
+
+
+// Function to derive the transform pattern from the route pattern
+func deriveTransformPattern(routePattern string) (string, error) {
+       log.Trace("entering deriveTransformPattern")
+       log.Debugf("deriveTransformPattern routePattern %s", routePattern)
+
+       // Append a slash to handle an edge case for matching a trailing capture group.
+       appendedSlash := false
+       if routePattern[len(routePattern)-1] != '/' {
+               routePattern = routePattern + "/"
+               appendedSlash = true
+               log.Debugf("deriveTransformPattern, append / routePattern %s", routePattern)
        }
 
        }
 
-       interfaceDescription := (*aefProfile.InterfaceDescriptions)[0]
-       firstIpv4Addr := *interfaceDescription.Ipv4Addr
-       firstPort := *interfaceDescription.Port
+       // Regular expression to match named capture groups
+       re := regexp.MustCompile(`\(\?<([^>]+)>([^\/]+)`)
+       // Find all matches in the route pattern
+       matches := re.FindAllStringSubmatch(routePattern, -1)
+
+       transformPattern := routePattern
+       for _, match := range matches {
+               // match[0] is the full match, match[1] is the capture group name, match[2] is the pattern
+               placeholder := fmt.Sprintf("$(uri_captures[\"%s\"])", match[1])
+               // Replace the capture group with the corresponding placeholder
+               transformPattern = strings.Replace(transformPattern, match[0], placeholder, 1)
+       }
+       log.Debugf("deriveTransformPattern transformPattern %s", transformPattern)
 
 
-       log.Debugf("findFirstAEFProfile firstIpv4Addr %s firstPort %d", firstIpv4Addr, firstPort)
+       if appendedSlash {
+               transformPattern = strings.TrimSuffix(transformPattern, "/")
+               log.Debugf("deriveTransformPattern, remove / transformPattern %s", transformPattern)
+       }
 
 
-       return firstIpv4Addr, firstPort, nil
+       return transformPattern, nil
 }
 
 // Update our exposures to point to Kong by replacing in incoming interface description with Kong interface descriptions.
 }
 
 // Update our exposures to point to Kong by replacing in incoming interface description with Kong interface descriptions.
index 5b949aa..d0fbe4f 100644 (file)
@@ -48,8 +48,9 @@ import (
 func main() {
        realConfigReader := &envreader.RealConfigReader{}
        myEnv, myPorts, err := realConfigReader.ReadDotEnv()
 func main() {
        realConfigReader := &envreader.RealConfigReader{}
        myEnv, myPorts, err := realConfigReader.ReadDotEnv()
+
        if err != nil {
        if err != nil {
-               log.Fatal("error loading environment file")
+               log.Fatalf("error loading environment file, %v", err)
                return
        }
 
                return
        }
 
@@ -87,7 +88,7 @@ func registerHandlers(e *echo.Echo, myEnv map[string]string, myPorts map[string]
        // Register ProviderManagement
        providerManagerSwagger, err := providermanagementapi.GetSwagger()
        if err != nil {
        // Register ProviderManagement
        providerManagerSwagger, err := providermanagementapi.GetSwagger()
        if err != nil {
-               log.Fatalf("error loading ProviderManagement swagger spec\n: %s", err)
+               log.Fatalf("error loading ProviderManagement swagger spec\n: %v", err)
                return err
        }
        providerManagerSwagger.Servers = nil
                return err
        }
        providerManagerSwagger.Servers = nil
@@ -99,7 +100,7 @@ func registerHandlers(e *echo.Echo, myEnv map[string]string, myPorts map[string]
        // Register PublishService
        publishServiceSwagger, err := publishserviceapi.GetSwagger()
        if err != nil {
        // Register PublishService
        publishServiceSwagger, err := publishserviceapi.GetSwagger()
        if err != nil {
-               log.Fatalf("error loading PublishService swagger spec\n: %s", err)
+               log.Fatalf("error loading PublishService swagger spec\n: %v", err)
                return err
        }
        publishServiceSwagger.Servers = nil
                return err
        }
        publishServiceSwagger.Servers = nil
@@ -116,7 +117,7 @@ func registerHandlers(e *echo.Echo, myEnv map[string]string, myPorts map[string]
        // Register InvokerManagement
        invokerManagerSwagger, err := invokermanagementapi.GetSwagger()
        if err != nil {
        // Register InvokerManagement
        invokerManagerSwagger, err := invokermanagementapi.GetSwagger()
        if err != nil {
-               log.Fatalf("error loading InvokerManagement swagger spec\n: %s", err)
+               log.Fatalf("error loading InvokerManagement swagger spec\n: %v", err)
                return err
        }
        invokerManagerSwagger.Servers = nil
                return err
        }
        invokerManagerSwagger.Servers = nil
@@ -128,7 +129,7 @@ func registerHandlers(e *echo.Echo, myEnv map[string]string, myPorts map[string]
        // Register DiscoverService
        discoverServiceSwagger, err := discoverserviceapi.GetSwagger()
        if err != nil {
        // Register DiscoverService
        discoverServiceSwagger, err := discoverserviceapi.GetSwagger()
        if err != nil {
-               log.Fatalf("error loading DiscoverService swagger spec\n: %s", err)
+               log.Fatalf("error loading DiscoverService swagger spec\n: %v", err)
                return err
        }
 
                return err
        }
 
@@ -155,7 +156,7 @@ func keepServerAlive() {
 }
 
 func hello(c echo.Context) error {
 }
 
 func hello(c echo.Context) error {
-       return c.String(http.StatusOK, "Hello, World!")
+       return c.String(http.StatusOK, "Hello World, from Service Manager!")
 }
 
 func getSwagger(c echo.Context) error {
 }
 
 func getSwagger(c echo.Context) error {
index 14acd57..8191c03 100644 (file)
@@ -85,6 +85,238 @@ func RegisterHandlers(e *echo.Echo) {
                return c.String(http.StatusCreated, string(body))
        })
 
                return c.String(http.StatusCreated, string(body))
        })
 
+       e.POST("/services/api_id_apiName_helloworld-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-v1-id_helloworld-id-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-v1_helloworld-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_helloworld-v1-id_helloworld-id-04478a3a-d0ef-5a05-a575-db5ee2e33403/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-no-version_helloworld-no-version-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_helloworld-no-version_helloworld-no-version-04478a3a-d0ef-5a05-a575-db5ee2e33403/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName1_helloworld-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName2_helloworld-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName1_app-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName2_app-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-v1-helloworld-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-v1-id-helloworld-id-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_helloworld-v1-id-helloworld-id-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-no-version-helloworld-no-version-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_helloworld-no-version-helloworld-no-version-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName-helloworld-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName1-helloworld-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName1-app-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName2-helloworld-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName2-app-port-30951-hash-04478a3a-d0ef-5a05-a575-db5ee2e33403/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName_helloworld-id/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_apiName_helloworld-no-version/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-v1-id_helloworld-id/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-v1_helloworld/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/services/api_id_helloworld-no-version_helloworld-no-version/routes", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_apiName_helloworld-id/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_apiName_helloworld-no-version/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_helloworld-v1-id_helloworld-id/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
+       e.POST("/routes/api_id_helloworld-no-version_helloworld-no-version/plugins", func(c echo.Context) error {
+               body, err := io.ReadAll(c.Request().Body)
+               if err != nil {
+                       return c.String(http.StatusInternalServerError, "Error reading request body")
+               }
+               return c.String(http.StatusCreated, string(body))
+       })
+
        e.GET("/services", func(c echo.Context) error {
                return c.String(http.StatusOK, "{}")
        })
        e.GET("/services", func(c echo.Context) error {
                return c.String(http.StatusOK, "{}")
        })
@@ -132,4 +364,45 @@ func RegisterHandlers(e *echo.Echo) {
        e.DELETE("/services/api_id_apiName2_app", func(c echo.Context) error {
                return c.NoContent(http.StatusNoContent)
        })
        e.DELETE("/services/api_id_apiName2_app", func(c echo.Context) error {
                return c.NoContent(http.StatusNoContent)
        })
-}
\ No newline at end of file
+
+       e.DELETE("/routes/api_id_apiName_helloworld-id", func(c echo.Context) error {
+               return c.NoContent(http.StatusNoContent)
+       })
+
+       e.DELETE("/routes/api_id_apiName_helloworld-no-version", func(c echo.Context) error {
+               return c.NoContent(http.StatusNoContent)
+       })
+
+       e.DELETE("/routes/api_id_helloworld-v1_helloworld", func(c echo.Context) error {
+               return c.NoContent(http.StatusNoContent)
+       })
+
+       e.DELETE("/services/api_id_apiName_helloworld-id", func(c echo.Context) error {
+               return c.NoContent(http.StatusNoContent)
+       })
+
+       e.DELETE("/services/api_id_apiName_helloworld-no-version", func(c echo.Context) error {
+               return c.NoContent(http.StatusNoContent)
+       })
+
+       e.DELETE("/services/api_id_helloworld-v1_helloworld", func(c echo.Context) error {
+               return c.NoContent(http.StatusNoContent)
+       })
+
+       e.DELETE("routes/api_id_helloworld-no-version_helloworld-no-version", func(c echo.Context) error {
+               return c.NoContent(http.StatusNoContent)
+       })
+
+       e.DELETE("services/api_id_helloworld-no-version_helloworld-no-version", func(c echo.Context) error {
+               return c.NoContent(http.StatusNoContent)
+       })
+
+       e.DELETE("/routes/api_id_helloworld-v1-id_helloworld-id", func(c echo.Context) error {
+               return c.NoContent(http.StatusNoContent)
+       })
+
+       e.DELETE("/services/api_id_helloworld-v1-id_helloworld-id", func(c echo.Context) error {
+               return c.NoContent(http.StatusNoContent)
+       })
+
+}