Repository: ovh/cds
Pull request #4921 (2020-01-27 09:35:41)

fix(api): deduplicate req from plugin's integration
Signed-off-by: Yvonnick Esnault <yvonnick.esnault@corp.ovh.com>


ovh-cds: 
CDS Report build-all-cds#12191.0 ✘  
* Build
  * Build and Package All ✔ 
  * Build Debpacker ✔ 
  * Build OS-Ansible-Inventory ✔ 
  * Build SMTPMock ✔ 
  * Lint UI Translate ✔ 
  * Test CLI ✔ 
  * Test Engine ✘  
  * Test SDK ✔ 
  * Test UI ✔ 


Patch:
diff --git a/contrib/integrations/arsenal/plugin-arsenal/Makefile b/contrib/integrations/arsenal/plugin-arsenal/Makefile
index c339610c4..5cb91ef3d 100644
--- a/contrib/integrations/arsenal/plugin-arsenal/Makefile
+++ b/contrib/integrations/arsenal/plugin-arsenal/Makefile
@@ -3,13 +3,11 @@
 VERSION := $(if ${CDS_SEMVER},${CDS_SEMVER},snapshot)
 GITHASH := $(if ${GIT_HASH},${GIT_HASH},`git log -1 --format="%H"`)
 BUILDTIME := `date "+%m/%d/%y-%H:%M:%S"`
+CDSCTL := $(if ${CDSCTL},${CDSCTL},cdsctl)
 
 TARGET_DIR = ./dist
 TARGET_NAME = plugin-arsenal
 
-PLUGIN_MANIFEST=`echo plugin.yml`
-export PLUGIN_MANIFEST
-
 define PLUGIN_MANIFEST_BINARY
 os: %os%
 arch: %arch%
@@ -50,12 +48,12 @@ build: $(TARGET_DIR)
 
 publish:
 	@echo "Updating plugin..."
-	cdsctl admin plugins import $(TARGET_DIR)/plugin.yml
+	$(CDSCTL) admin plugins import $(TARGET_DIR)/$(TARGET_NAME).yml
 	@for GOOS in $(TARGET_OS); do \
 		for GOARCH in $(TARGET_ARCH); do \
 			EXTENSION=""; \
 			if test "$$GOOS" = "windows" ; then EXTENSION=".exe"; fi; \
 			echo "Updating plugin binary $(TARGET_NAME)-$$GOOS-$$GOARCH$$EXTENSION"; \
-			cdsctl admin plugins binary-add arsenal-deployment-plugin $(TARGET_DIR)/plugin-$$GOOS-$$GOARCH.yml $(TARGET_DIR)/$(TARGET_NAME)-$$GOOS-$$GOARCH$$EXTENSION; \
+			$(CDSCTL) admin plugins binary-add arsenal-deployment-plugin $(TARGET_DIR)/$(TARGET_NAME)-$$GOOS-$$GOARCH.yml $(TARGET_DIR)/$(TARGET_NAME)-$$GOOS-$$GOARCH$$EXTENSION; \
 		done; \
 	done
diff --git a/contrib/integrations/hello/plugin-hello-deployment/main.go b/contrib/integrations/hello/plugin-hello-deployment/main.go
index 7098f0a2d..70be5d304 100644
--- a/contrib/integrations/hello/plugin-hello-deployment/main.go
+++ b/contrib/integrations/hello/plugin-hello-deployment/main.go
@@ -32,41 +32,41 @@ Hello deployment plugin must configured as following (content of hello-deploymen
 $ cdsctl admin plugins import hello-deployment-plugin.yml
 
 Build the present binaries and import in CDS (content of hello-deployment-plugin-bin.yml):
-	os: linux
-	arch: amd64
-	cmd: <path-to-binary-file>
+  os: linux
+  arch: amd64
+  cmd: <path-to-binary-file>
 
 $ cdsctl admin plugins binary-add hello-deployment-plugin hello-deployment-plugin-bin.yml <path-to-binary-file>
 
 Hello integration must configured as following (content of hello-integration.yml)
-	name: Hello
-	author: "Username Lastname"
-	default_config: {}
-	deployment_default_config:
-	deployment.token:
-		value: ""
-		type: password
-	retry.delay:
-		value: "10"
-		type: string
-	retry.max:
-		value: "30"
-		type: string
-	version:
-		value: '{{.cds.version}}'
-		type: string
-	plugin: hello-deployment-plugin
-	public_configurations:
-	hello-integration-dev:
-		host:
-		value: http://hello.your-deployment-system.dev.local
-		type: string
-	hello-integration-prod:
-		host:
-		value: http://hello.your-deployment-system.prod.local
-		type: string
-	deployment: true
-	public: true
+  name: Hello
+  author: "Username Lastname"
+  default_config: {}
+  deployment_default_config:
+  deployment.token:
+    value: ""
+    type: password
+  retry.delay:
+    value: "10"
+    type: string
+  retry.max:
+    value: "30"
+    type: string
+  version:
+    value: '{{.cds.version}}'
+    type: string
+  plugin: hello-deployment-plugin
+  public_configurations:
+    hello-integration-dev:
+      host:
+        value: http://hello.your-deployment-system.dev.local
+        type: string
+    hello-integration-prod:
+      host:
+        value: http://hello.your-deployment-system.prod.local
+        type: string
+  deployment: true
+  public: true
 
 $ cdsctl admin integration-model import hello-integration.yml
 
diff --git a/docs/content/docs/integrations/kubernetes/kubernetes_deployment.md b/docs/content/docs/integrations/kubernetes/kubernetes_deployment.md
index ffadb5077..89a3c6395 100644
--- a/docs/content/docs/integrations/kubernetes/kubernetes_deployment.md
+++ b/docs/content/docs/integrations/kubernetes/kubernetes_deployment.md
@@ -184,13 +184,11 @@ deployment_default_config:
   helm_values:
     value: ""
     type: string
-    description: specify helm values in a YAML file or a URL to configure/override
-      your helm chart
+    description: specify helm values in a YAML file or a URL to configure/override your helm chart
   namespace:
     value: default
     type: string
-    description: Kubernetes namespace in which you want to deploy your components
-      (OPTIONAL)
+    description: Kubernetes namespace in which you want to deploy your components (OPTIONAL)
   timeout:
     value: "180"
     type: string
@@ -198,18 +196,18 @@ deployment_default_config:
 public_configurations:
   your-public-myk8s-integration:
     "api_url":
-        value: https://your-k8s.localhost.local
-        type: string
+      value: https://your-k8s.localhost.local
+      type: string
     "ca_certificate":
-        value: |-
-          -----BEGIN CERTIFICATE-----
-          XXX
-          -----END CERTIFICATE-----
-        type: text
-        description: Certificate Authority bundle (PEM format)
+      value: |-
+        -----BEGIN CERTIFICATE-----
+        XXX
+        -----END CERTIFICATE-----
+      type: text
+      description: Certificate Authority bundle (PEM format)
     "token":
-        value: XXX
-        type: string
+      value: XXX
+      type: string
 ```
 
 Import the integration with :
diff --git a/docs/content/docs/integrations/rabbitmq.md b/docs/content/docs/integrations/rabbitmq.md
index 6471448c0..b69875a11 100644
--- a/docs/content/docs/integrations/rabbitmq.md
+++ b/docs/content/docs/integrations/rabbitmq.md
@@ -72,23 +72,23 @@ public_configurations:
       type: password
       value: xxxxxxxx
     "binding_key":
-        type: string
-        value: "test-key"
+      type: string
+      value: "test-key"
     "consumer_tag":
-        type: string
-        value: "simple-consumer"
+      type: string
+      value: "simple-consumer"
     "exchange_name":
-        type: string
-        value: "test-exchange"
+      type: string
+      value: "test-exchange"
     "exchange_type":
-        type: string
-        value: "direct"
+      type: string
+      value: "direct"
     "integration":
-        type: string
-        value: "RabbitMQ"
+      type: string
+      value: "RabbitMQ"
     "queue":
-        type: string
-        value: "test-queue"
+      type: string
+      value: "test-queue"
 ```
 
 Import the integration with :
diff --git a/engine/api/project_integration.go b/engine/api/project_integration.go
index 46ad1dad0..783914a92 100644
--- a/engine/api/project_integration.go
+++ b/engine/api/project_integration.go
@@ -74,6 +74,7 @@ func (api *API) putProjectIntegrationHandler() service.Handler {
 					}
 				}
 			}
+			projectIntegration.Config[kkBody] = c
 		}
 
 		tx, errT := api.mustDB().Begin()
diff --git a/engine/api/workflow/process_requirements.go b/engine/api/workflow/process_requirements.go
index f2f86e362..159f5c4f0 100644
--- a/engine/api/workflow/process_requirements.go
+++ b/engine/api/workflow/process_requirements.go
@@ -23,10 +23,17 @@ func processNodeJobRunRequirements(ctx context.Context, db gorp.SqlExecutor, j s
 	var model string
 	var tmp = sdk.ParametersToMap(run.BuildParameters)
 
+	pluginsRequirements := []sdk.Requirement{}
 	for i := range integrationPluginBinaries {
-		j.Action.Requirements = append(j.Action.Requirements, integrationPluginBinaries[i].Requirements...)
+		pluginsRequirements = append(pluginsRequirements, integrationPluginBinaries[i].Requirements...)
 	}
 
+	// as some plugin binaries can have same requirement, we deduplicate them
+	pluginsRequirements = sdk.RequirementListDeduplicate(pluginsRequirements)
+
+	// then add plugins requirement to the action requirement
+	j.Action.Requirements = append(j.Action.Requirements, pluginsRequirements...)
+
 	for _, v := range j.Action.Requirements {
 		name, errName := interpolate.Do(v.Name, tmp)
 		if errName != nil {
diff --git a/engine/hooks/outgoing_hooks.go b/engine/hooks/outgoing_hooks.go
index b48e507ad..ac28f2a2a 100644
--- a/engine/hooks/outgoing_hooks.go
+++ b/engine/hooks/outgoing_hooks.go
@@ -213,10 +213,6 @@ func (s *Service) doOutgoingWorkflowExecution(ctx context.Context, t *sdk.TaskEx
 			} else {
 				log.Error(ctx, "Hooks> doOutgoingWorkflowExecution> Cannot unmarshall payload %s", err)
 			}
-
-			if err := json.Unmarshal([]byte(payloadstr), &payloadValues); err != nil {
-				return sdk.WrapError(handleError(ctx, err), "cannot unmarshall payload")
-			}
 		}
 	}
 
@@ -262,7 +258,7 @@ func (s *Service) doOutgoingWebHookExecution(ctx context.Context, t *sdk.TaskExe
 		return nil
 	}
 
-	if wr.Status != sdk.StatusDisabled {
+	if wr.Status != sdk.StatusBuilding {
 		log.Error(ctx, "Hooks> workflow %s/%s #%s status: %s", pkey, workflow, run, wr.Status)
 		return nil
 	}
diff --git a/engine/worker/internal/action/builtin_artifact_download.go b/engine/worker/internal/action/builtin_artifact_download.go
index ef84ed6dd..2c4d45e12 100644
--- a/engine/worker/internal/action/builtin_artifact_download.go
+++ b/engine/worker/internal/action/builtin_artifact_download.go
@@ -104,7 +104,7 @@ func RunArtifactDownload(ctx context.Context, wk workerruntime.Runtime, a sdk.Ac
 				wk.SendLog(ctx, workerruntime.LevelError, res.Reason)
 				return
 			}
-			wk.SendLog(ctx, workerruntime.LevelInfo, fmt.Sprintf("downloading artifact %s from workflow %s/%s on run %d...", destFile, project, workflow, n))
+			wk.SendLog(ctx, workerruntime.LevelInfo, fmt.Sprintf("Downloading artifact %s from workflow %s/%s on run %d...", destFile, project, workflow, n))
 			if err := wk.Client().WorkflowNodeRunArtifactDownload(project, workflow, *a, f); err != nil {
 				res.Status = sdk.StatusFail
 				res.Reason = err.Error()
diff --git a/engine/worker/internal/action/builtin_artifact_upload.go b/engine/worker/internal/action/builtin_artifact_upload.go
index 0a5624da3..f88a11b89 100644
--- a/engine/worker/internal/action/builtin_artifact_upload.go
+++ b/engine/worker/internal/action/builtin_artifact_upload.go
@@ -79,10 +79,11 @@ func RunArtifactUpload(ctx context.Context, wk workerruntime.Runtime, a sdk.Acti
 	wg.Add(len(filesPath))
 	for _, p := range filesPath {
 		go func(path string) {
-			log.Debug("Uploading %s projectKey:%v integrationName:%v job:%d", path, projectKey, integrationName, jobID)
+			log.Debug("worker.RunArtifactUpload> Uploading %s projectKey:%v integrationName:%v job:%d", path, projectKey, integrationName, jobID)
 			defer wg.Done()
 			throughTempURL, duration, err := wk.Client().QueueArtifactUpload(ctx, projectKey, integrationName, jobID, tag.Value, path)
 			if err != nil {
+				log.Warning(ctx, "worker.RunArtifactUpload> QueueArtifactUpload(%s, %s, %d, %s, %s) failed: %v", projectKey, integrationName, jobID, tag.Value, path, err)
 				chanError <- sdk.WrapError(err, "Error while uploading artifact %s", path)
 				wgErrors.Add(1)
 				return
diff --git a/engine/worker/internal/requirement.go b/engine/worker/internal/requirement.go
index de7df2f73..e3bbff1c2 100644
--- a/engine/worker/internal/requirement.go
+++ b/engine/worker/internal/requirement.go
@@ -125,13 +125,8 @@ func checkModelRequirement(w *CurrentWorker, r sdk.Requirement) (bool, error) {
 }
 
 func checkNetworkAccessRequirement(w *CurrentWorker, r sdk.Requirement) (bool, error) {
-	conn, err := net.DialTimeout("tcp", r.Value, 10*time.Second)
-	if err != nil {
-		return false, nil
-	}
-	conn.Close()
-
-	return true, nil
+	isValid := sdk.CheckNetworkAccessRequirement(r)
+	return isValid, nil
 }
 
 func checkServiceRequirement(w *CurrentWorker, r sdk.Requirement) (bool, error) {
diff --git a/go.mod b/go.mod
index 80ae37bf1..6e17789dc 100644
--- a/go.mod
+++ b/go.mod
@@ -62,8 +62,9 @@ require (
 	github.com/go-sql-driver/mysql v1.4.1 // indirect
 	github.com/go-stomp/stomp v1.0.1 // indirect
 	github.com/gocql/gocql v0.0.0-20181018123354-22229812a83e // indirect
-	github.com/golang/mock v1.3.1
+	github.com/golang/mock v1.4.0
 	github.com/golang/protobuf v1.3.2
+	github.com/google/go-cmp v0.4.0
 	github.com/googleapis/gnostic v0.1.0 // indirect
 	github.com/gophercloud/gophercloud v0.0.0-20190504011306-6f9faf57fddc
 	github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
diff --git a/go.sum b/go.sum
index aacd80db4..28c26569c 100644
--- a/go.sum
+++ b/go.sum
@@ -185,6 +185,8 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
@@ -199,6 +201,8 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
@@ -651,6 +655,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
 golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
@@ -671,7 +676,9 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0 h1:Dh6fw+p6FyRl5x/FvNswO1ji0lIGzm3KP8Y9VkS9PTE=
 golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -769,5 +776,7 @@ labix.org/v2/mgo v0.0.0-20140701140051-000000000287/go.mod h1:Lg7AYkt1uXJoR9oeSZ
 launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbceQyw/gSGoXfH/UPoTGduL54=
 launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
diff --git a/sdk/cdsclient/client_queue.go b/sdk/cdsclient/client_queue.go
index 6d995601a..9b79b913a 100644
--- a/sdk/cdsclient/client_queue.go
+++ b/sdk/cdsclient/client_queue.go
@@ -489,8 +489,11 @@ func (c *client) queueDirectArtifactUpload(projectKey, integrationName string, n
 		_, code, err = c.UploadMultiPart("POST", uri, body,
 			SetHeader("Content-Disposition", "attachment; filename="+name),
 			SetHeader("Content-Type", writer.FormDataContentType()))
-		if err == nil && code < 300 {
-			return nil
+		if err == nil {
+			if code < 400 {
+				return nil
+			}
+			err = fmt.Errorf("Error: HTTP code status %d", code)
 		}
 		time.Sleep(3 * time.Second)
 	}
diff --git a/sdk/cdsclient/mock_cdsclient/interface_mock.go b/sdk/cdsclient/mock_cdsclient/interface_mock.go
index e99b253b4..170932233 100644
--- a/sdk/cdsclient/mock_cdsclient/interface_mock.go
+++ b/sdk/cdsclient/mock_cdsclient/interface_mock.go
@@ -11,7 +11,7 @@ import (
 	sdk "github.com/ovh/cds/sdk"
 	cdsclient "github.com/ovh/cds/sdk/cdsclient"
 	venom "github.com/ovh/venom"
-	go_coverage "github.com/sguiheux/go-coverage"
+	coverage "github.com/sguiheux/go-coverage"
 	io "io"
 	http "net/http"
 	reflect "reflect"
@@ -2892,7 +2892,7 @@ func (mr *MockQueueClientMockRecorder) QueueJobSendSpawnInfo(ctx, id, in interfa
 }
 
 // QueueSendCoverage mocks base method
-func (m *MockQueueClient) QueueSendCoverage(ctx context.Context, id int64, report go_coverage.Report) error {
+func (m *MockQueueClient) QueueSendCoverage(ctx context.Context, id int64, report coverage.Report) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "QueueSendCoverage", ctx, id, report)
 	ret0, _ := ret[0].(error)
@@ -3134,6 +3134,21 @@ func (mr *MockUserClientMockRecorder) UpdateFavorite(params interface{}) *gomock
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFavorite", reflect.TypeOf((*MockUserClient)(nil).UpdateFavorite), params)
 }
 
+// UserGetSchema mocks base method
+func (m *MockUserClient) UserGetSchema() (sdk.SchemaResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "UserGetSchema")
+	ret0, _ := ret[0].(sdk.SchemaResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// UserGetSchema indicates an expected call of UserGetSchema
+func (mr *MockUserClientMockRecorder) UserGetSchema() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserGetSchema", reflect.TypeOf((*MockUserClient)(nil).UserGetSchema))
+}
+
 // MockWorkerClient is a mock of WorkerClient interface
 type MockWorkerClient struct {
 	ctrl     *gomock.Controller
@@ -6430,7 +6445,7 @@ func (mr *MockInterfaceMockRecorder) QueueJobSendSpawnInfo(ctx, id, in interface
 }
 
 // QueueSendCoverage mocks base method
-func (m *MockInterface) QueueSendCoverage(ctx context.Context, id int64, report go_coverage.Report) error {
+func (m *MockInterface) QueueSendCoverage(ctx context.Context, id int64, report coverage.Report) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "QueueSendCoverage", ctx, id, report)
 	ret0, _ := ret[0].(error)
@@ -6723,6 +6738,21 @@ func (mr *MockInterfaceMockRecorder) UpdateFavorite(params interface{}) *gomock.
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateFavorite", reflect.TypeOf((*MockInterface)(nil).UpdateFavorite), params)
 }
 
+// UserGetSchema mocks base method
+func (m *MockInterface) UserGetSchema() (sdk.SchemaResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "UserGetSchema")
+	ret0, _ := ret[0].(sdk.SchemaResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// UserGetSchema indicates an expected call of UserGetSchema
+func (mr *MockInterfaceMockRecorder) UserGetSchema() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserGetSchema", reflect.TypeOf((*MockInterface)(nil).UserGetSchema))
+}
+
 // WorkerModelBook mocks base method
 func (m *MockInterface) WorkerModelBook(groupName, name string) error {
 	m.ctrl.T.Helper()
@@ -7875,7 +7905,7 @@ func (mr *MockWorkerInterfaceMockRecorder) QueueJobSendSpawnInfo(ctx, id, in int
 }
 
 // QueueSendCoverage mocks base method
-func (m *MockWorkerInterface) QueueSendCoverage(ctx context.Context, id int64, report go_coverage.Report) error {
+func (m *MockWorkerInterface) QueueSendCoverage(ctx context.Context, id int64, report coverage.Report) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "QueueSendCoverage", ctx, id, report)
 	ret0, _ := ret[0].(error)
diff --git a/sdk/hatchery/hatchery.go b/sdk/hatchery/hatchery.go
index e68b6c882..0d085e440 100644
--- a/sdk/hatchery/hatchery.go
+++ b/sdk/hatchery/hatchery.go
@@ -273,8 +273,13 @@ func canRunJob(ctx context.Context, h Interface, j workerStarterRequest) bool {
 			return false
 		}
 
-		// Skip network access requirement as we can't check it
-		if r.Type == sdk.NetworkAccessRequirement || r.Type == sdk.PluginRequirement || r.Type == sdk.ServiceRequirement || r.Type == sdk.MemoryRequirement {
+		if r.Type == sdk.NetworkAccessRequirement && !sdk.CheckNetworkAccessRequirement(r) {
+			log.Debug("canRunJob> %d - job %d - network requirement failed: %v", j.timestamp, j.id, r.Value)
+			return false
+		}
+
+		// Skip others requirement as we can't check it
+		if r.Type == sdk.PluginRequirement || r.Type == sdk.ServiceRequirement || r.Type == sdk.MemoryRequirement {
 			log.Debug("canRunJob> %d - job %d - job with service, plugin, network or memory requirement. Skip these check as we can't checkt it on hatchery routine", j.timestamp, j.id)
 			continue
 		}
@@ -354,8 +359,13 @@ func canRunJobWithModel(ctx context.Context, h InterfaceWithModels, j workerStar
 			return false
 		}
 
-		// Skip network access requirement as we can't check it
-		if r.Type == sdk.NetworkAccessRequirement || r.Type == sdk.PluginRequirement || r.Type == sdk.ServiceRequirement || r.Type == sdk.MemoryRequirement {
+		if r.Type == sdk.NetworkAccessRequirement && !sdk.CheckNetworkAccessRequirement(r) {
+			log.Debug("canRunJob> %d - job %d - network requirement failed: %v", j.timestamp, j.id, r.Value)
+			return false
+		}
+
+		// Skip other requirement as we can't check it
+		if r.Type == sdk.PluginRequirement || r.Type == sdk.ServiceRequirement || r.Type == sdk.MemoryRequirement {
 			log.Debug("canRunJob> %d - job %d - job with service, plugin, network or memory requirement. Skip these check as we can't check it on hatchery routine", j.timestamp, j.id)
 			continue
 		}
diff --git a/sdk/integration.go b/sdk/integration.go
index 1a73134ae..dc80ddad0 100644
--- a/sdk/integration.go
+++ b/sdk/integration.go
@@ -155,7 +155,7 @@ func DefaultIfEmptyStorage(integrationName string) string {
 	return integrationName
 }
 
-// IntegrationConfig represent the configuration of a plateform
+// IntegrationConfig represent the configuration of an integration
 type IntegrationConfig map[string]IntegrationConfigValue
 
 // Clone return a copy of the config (with a copy of the underlying data structure)
diff --git a/sdk/requirement.go b/sdk/requirement.go
index 0208b13a1..809c908de 100644
--- a/sdk/requirement.go
+++ b/sdk/requirement.go
@@ -1,5 +1,10 @@
 package sdk
 
+import (
+	"net"
+	"time"
+)
+
 const (
 	//BinaryRequirement refers to the need to a specific binary on host running the action
 	BinaryRequirement = "binary"
@@ -33,6 +38,21 @@ func (l RequirementList) Values() []string {
 	return values
 }
 
+// RequirementListDeduplicate returns requirements list without duplicate values.
+func RequirementListDeduplicate(l RequirementList) RequirementList {
+	m := map[string]Requirement{}
+
+	for i := range l {
+		m[l[i].Name+l[i].Type+l[i].Value] = l[i]
+	}
+
+	newList := make([]Requirement, 0, len(m))
+	for i := range m {
+		newList = append(newList, m[i])
+	}
+	return newList
+}
+
 // IsValid returns requirement list validity.
 func (l RequirementList) IsValid() error {
 	// check requirement unicity
@@ -155,3 +175,14 @@ func (a *Action) Requirement(name string, t string, value string) *Action {
 	a.Requirements = append(a.Requirements, r)
 	return a
 }
+
+// CheckNetworkAccessRequirement returns true if req.Value can Dial
+func CheckNetworkAccessRequirement(req Requirement) bool {
+	conn, err := net.DialTimeout("tcp", req.Value, 10*time.Second)
+	if err != nil {
+		return false
+	}
+	conn.Close()
+
+	return true
+}


Test patch:
diff --git a/sdk/requirement_test.go b/sdk/requirement_test.go
new file mode 100644
index 000000000..2cc0cd28b
--- /dev/null
+++ b/sdk/requirement_test.go
@@ -0,0 +1,102 @@
+package sdk
+
+import (
+	"testing"
+)
+
+func TestRequirementListDeduplicate(t *testing.T) {
+	type args struct {
+		l RequirementList
+	}
+	tests := []struct {
+		name string
+		args args
+		want RequirementList
+	}{
+		{
+			name: "test1",
+			args: args{
+				l: RequirementList{
+					{
+						Name:  "namea",
+						Type:  NetworkAccessRequirement,
+						Value: "valuea",
+					},
+					{
+						Name:  "namea",
+						Type:  NetworkAccessRequirement,
+						Value: "valuea",
+					},
+					{
+						Name:  "nameb",
+						Type:  NetworkAccessRequirement,
+						Value: "valueb",
+					},
+				},
+			},
+			want: RequirementList{
+				{
+					Name:  "namea",
+					Type:  NetworkAccessRequirement,
+					Value: "valuea",
+				},
+				{
+					Name:  "nameb",
+					Type:  NetworkAccessRequirement,
+					Value: "valueb",
+				},
+			},
+		},
+		{
+			name: "test2",
+			args: args{
+				l: RequirementList{
+					{
+						Name:  "namea",
+						Type:  NetworkAccessRequirement,
+						Value: "valuea",
+					},
+					{
+						Name:  "nameb",
+						Type:  NetworkAccessRequirement,
+						Value: "valueb",
+					},
+					{
+						Name:  "nameb",
+						Type:  NetworkAccessRequirement,
+						Value: "valueb",
+					},
+				},
+			},
+			want: RequirementList{
+				{
+					Name:  "nameb",
+					Type:  NetworkAccessRequirement,
+					Value: "valueb",
+				},
+				{
+					Name:  "namea",
+					Type:  NetworkAccessRequirement,
+					Value: "valuea",
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := RequirementListDeduplicate(tt.args.l)
+			for _, r := range tt.want {
+				var found bool
+				for _, g := range got {
+					if r.Type == g.Type && r.Value == g.Value && r.Name == g.Name {
+						found = true
+						break
+					}
+				}
+				if !found {
+					t.Errorf("RequirementListDeduplicate() = %v, want %v - not found: %v", got, tt.want, r)
+				}
+			}
+		})
+	}
+}