diff --git a/Makefile b/Makefile index 91131fa46f..8bcd505a2d 100755 --- a/Makefile +++ b/Makefile @@ -40,17 +40,13 @@ test-unit: go test ./pkg/pipeline test-integration: - export INTEGRATION_TEST_ENV_ID=$(docker run --env TEST_BRANCH=$TEST_BRANCH --env LATEST_HASH=$LATEST_HASH --privileged -d --name dind-test -v $PWD/tests/integrationTesting/:/tmp/ docker:dind) - docker exec ${INTEGRATION_TEST_ENV_ID} sh /tmp/create-test-env.sh - docker exec ${INTEGRATION_TEST_ENV_ID} sh /tests/integrationTesting/run-integration-test.sh - + docker run --env-file=wireNilChecker.env --privileged -d --name dind-test -v $(PWD)/:/wirenil/:ro -v $(PWD)/temp/:/tempfile docker:dind + docker exec dind-test sh -c "mkdir test && cp -r wirenil/* test/ && ./test/tests/integrationTesting/exportEnvsExecuteWireNilChecker.sh" run: build ./devtron - .PHONY: build docker-build-image: build docker build -t devtron:$(TAG) . - .PHONY: build, all, wire, clean, run, set-docker-build-env, docker-build-push, devtron, docker-build-push: docker-build-image docker tag devtron:${TAG} ${REGISTRY}/devtron:${TAG} diff --git a/WiringNilCheck.go b/WiringNilCheck.go new file mode 100755 index 0000000000..1f2b5907cc --- /dev/null +++ b/WiringNilCheck.go @@ -0,0 +1,138 @@ +package main + +import ( + "fmt" + "log" + "os" + "reflect" + "strings" + "unsafe" +) + +func CheckIfNilInWire() { + app, err := InitializeApp() + if err != nil { + log.Panic(err) + } + nilFieldsMap := make(map[string]bool) + checkNilFields(app, nilFieldsMap) + fmt.Println("NIL Fields present in impls are: ", nilFieldsMap) + //Writes the length of nilFieldsMap to a file (e.g., output.env) so that we can export this file's data in a pre-CI pipeline bash script and fail the pre-CI pipeline if the length of nilFieldsMap is greater than zero. + err = writeResultToFile(len(nilFieldsMap)) + if err != nil { + return + } +} + +func checkNilFields(obj interface{}, nilObjMap map[string]bool) { + val := reflect.ValueOf(obj) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if val.Kind() != reflect.Struct { + return + } + valName := val.Type().Name() + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + fieldName := val.Type().Field(i).Name + pkgPath := val.Type().PkgPath() + if pkgPath != "main" && !strings.Contains(pkgPath, "devtron-labs/devtron") { + //package not from this repo, ignoring + continue + } + if skipUnnecessaryFieldsForCheck(fieldName, valName) { // skip unnecessary fileds and values + continue + } + if !canFieldTypeBeNil(field) { // field can not be nil, skip + continue + } else if field.IsNil() { // check if the field is nil + mapEntry := fmt.Sprintf("%s.%s", valName, fieldName) + nilObjMap[mapEntry] = true + continue + } + if canSkipFieldStructCheck(fieldName, valName) { + continue + } + if !isExported(fieldName) && !field.CanInterface() { + unexportedField := GetUnexportedField(field) + checkNilFields(unexportedField, nilObjMap) + } else { + // Recurse + checkNilFields(field.Interface(), nilObjMap) + } + } +} + +func canFieldTypeBeNil(field reflect.Value) bool { + kind := field.Kind() + switch kind { + case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, + reflect.Interface, reflect.Slice: + return true + default: //other types can not be nil + return false + } +} + +func canSkipFieldStructCheck(fieldName, valName string) bool { + fieldName = strings.ToLower(fieldName) + valName = strings.ToLower(valName) + if valName == "githubclient" && (fieldName == "client" || fieldName == "gitopshelper") { + return true + } + for _, str := range []string{"logger", "dbconnection", "syncedenforcer"} { + if fieldName == str { + return true + } + } + return false +} + +func skipUnnecessaryFieldsForCheck(fieldName, valName string) bool { + fieldName = strings.ToLower(fieldName) + valName = strings.ToLower(valName) + if valName == "cicdconfig" { + return true + } + fieldAndValName := map[string][]string{ + "app": {"enforcerv2", "server"}, + "gitfactory": {"client"}, + "argocdconnectionmanagerimpl": {"argocdsettings"}, + "enforcerimpl": {"cache", "enforcerv2"}, + "helmappclientimpl": {"applicationserviceclient"}, + "modulecronserviceimpl": {"cron"}, + "oteltracingserviceimpl": {"traceprovider"}, + "terminalaccessrepositoryimpl": {"templatescache"}, + } + if _, ok := fieldAndValName[valName]; ok { + for _, ignoreFieldName := range fieldAndValName[valName] { + if ignoreFieldName == fieldName { + return true + } + } + } + return false +} +func GetUnexportedField(field reflect.Value) interface{} { + return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface() +} + +func isExported(fieldName string) bool { + return strings.ToUpper(fieldName[0:1]) == fieldName[0:1] +} + +func writeResultToFile(data int) error { + file, err := os.Create("/test/output.env") + if err != nil { + log.Println("Failed to create file:", err) + return err + } + defer file.Close() + _, err = file.WriteString(fmt.Sprintf("OUTPUT=%d", data)) + if err != nil { + log.Println("Failed to write to file:", err) + return err + } + return nil +} diff --git a/main.go b/main.go index 2ebf6851f6..c5061fe4f9 100755 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( "fmt" _ "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" _ "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + util2 "github.com/devtron-labs/devtron/util" "log" "os" "os/signal" @@ -28,7 +29,15 @@ import ( ) func main() { - + globalEnvVariables, err := util2.GetEnvironmentVariables() + if err != nil { + log.Println("error while getting env variables reason:", err) + return + } + if globalEnvVariables.GlobalEnvVariables.ExecuteWireNilChecker { + CheckIfNilInWire() + return + } app, err := InitializeApp() if err != nil { log.Panic(err) diff --git a/pkg/cluster/ClusterService.go b/pkg/cluster/ClusterService.go index 45e561a59e..e2ea1e1271 100644 --- a/pkg/cluster/ClusterService.go +++ b/pkg/cluster/ClusterService.go @@ -197,7 +197,6 @@ type ClusterServiceImpl struct { userAuthRepository repository3.UserAuthRepository userRepository repository3.UserRepository roleGroupRepository repository3.RoleGroupRepository - *ClusterRbacServiceImpl } func NewClusterServiceImpl(repository repository.ClusterRepository, logger *zap.SugaredLogger, @@ -212,9 +211,6 @@ func NewClusterServiceImpl(repository repository.ClusterRepository, logger *zap. userAuthRepository: userAuthRepository, userRepository: userRepository, roleGroupRepository: roleGroupRepository, - ClusterRbacServiceImpl: &ClusterRbacServiceImpl{ - logger: logger, - }, } go clusterService.buildInformer() return clusterService diff --git a/tests/integrationTesting/create-test-env.sh b/tests/integrationTesting/create-test-env.sh old mode 100644 new mode 100755 index df01de3ccd..133740caae --- a/tests/integrationTesting/create-test-env.sh +++ b/tests/integrationTesting/create-test-env.sh @@ -16,8 +16,13 @@ install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl kubectl create ns devtroncd kubectl create ns devtron-cd kubectl create ns devtron-ci +kubectl -n devtroncd create cm git-hash-cm --from-literal=GIT_HASH=$GIT_HASH kubectl -n devtroncd apply -f $PWD/tests/integrationTesting/postgresql-secret.yaml kubectl -ndevtroncd apply -f $PWD/tests/integrationTesting/postgresql.yaml +kubectl -n devtroncd apply -f $PWD/tests/integrationTesting/devtron-secret.yaml +kubectl -n devtroncd apply -f $PWD/tests/integrationTesting/nats-server.yaml +# we are copying sql scripts into node container and this conainer's name is fixed +docker cp $PWD/scripts/sql/ k3d-it-cluster-server-0:./tmp/scripts yq '(select(.metadata.name == "postgresql-migrate-devtron") | .spec.template.spec.containers[0].env[0].value) = env(TEST_BRANCH)' $PWD/tests/integrationTesting/migrator.yaml -i yq '(select(.metadata.name == "postgresql-migrate-devtron") | .spec.template.spec.containers[0].env[9].value) = env(LATEST_HASH)' $PWD/tests/integrationTesting/migrator.yaml -i kubectl -ndevtroncd apply -f $PWD/tests/integrationTesting/migrator.yaml @@ -50,4 +55,4 @@ helm dependency up helm template devtron . --set installer.modules={cicd} -s templates/workflow.yaml >./argo_wf.yaml kubectl apply -f ./argo_wf.yaml while [ ! $(kubectl -n argo get deployment workflow-controller -o jsonpath="{.status.readyReplicas}") ]; do sleep 10; done -cd $PWD \ No newline at end of file +cd $PWD diff --git a/tests/integrationTesting/devtron-secret.yaml b/tests/integrationTesting/devtron-secret.yaml new file mode 100755 index 0000000000..921fbabb14 --- /dev/null +++ b/tests/integrationTesting/devtron-secret.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +data: + PG_PASSWORD: c2hhcmVkLWRldnRyb24tcGc= +kind: Secret +metadata: + annotations: + meta.helm.sh/release-name: orchestrator-oss-shared-cd-dcd + meta.helm.sh/release-namespace: devtroncd + creationTimestamp: "2024-03-19T13:58:54Z" + labels: + app.kubernetes.io/managed-by: Helm + name: devtron-secret + namespace: devtroncd +type: Opaque diff --git a/tests/integrationTesting/exportEnvsExecuteWireNilChecker.sh b/tests/integrationTesting/exportEnvsExecuteWireNilChecker.sh new file mode 100755 index 0000000000..0313de76c7 --- /dev/null +++ b/tests/integrationTesting/exportEnvsExecuteWireNilChecker.sh @@ -0,0 +1,10 @@ +cd test +./tests/integrationTesting/create-test-env.sh +./tests/integrationTesting/run-integration-test.sh +touch output.env +export NODE_IP_ADDRESS=$(kubectl get node --no-headers -o custom-columns=INTERNAL-IP:status.addresses[0].address) +export PG_ADDR=$NODE_IP_ADDRESS +export NATS_SERVER_HOST=nats://$NODE_IP_ADDRESS:30236 +go mod tidy +go run . +cp output.env ../tempfile \ No newline at end of file diff --git a/tests/integrationTesting/migrator.yaml b/tests/integrationTesting/migrator.yaml index 33cd1d68af..a34663f404 100644 --- a/tests/integrationTesting/migrator.yaml +++ b/tests/integrationTesting/migrator.yaml @@ -7,14 +7,11 @@ spec: spec: containers: - name: postgresql-migrate-devtron - image: quay.io/devtron/migrator:71748de9-149-11112 + image: quay.io/devtron/migrator:e026843e-866-11925 + volumeMounts: + - mountPath: /tmp/app/ + name: sql-scripts-volume env: - - name: GIT_BRANCH - value: main - - name: SCRIPT_LOCATION - value: scripts/sql/ - - name: GIT_REPO_URL - value: https://github.com/devtron-labs/devtron.git - name: DB_TYPE value: postgres - name: DB_USER_NAME @@ -27,12 +24,17 @@ spec: value: orchestrator - name: MIGRATE_TO_VERSION value: "0" - - name: GIT_HASH - value: be7da471e45a501eba19eaa5f8d08dfe5601598d + - name: SCRIPT_MOUNTED + value: "true" envFrom: - secretRef: name: postgresql-migrator restartPolicy: OnFailure + volumes: + - name: sql-scripts-volume + hostPath: + path: /tmp/scripts/ + type: DirectoryOrCreate backoffLimit: 20 activeDeadlineSeconds: 1500 --- @@ -45,7 +47,7 @@ spec: spec: containers: - name: postgresql-migrate-casbin - image: quay.io/devtron/migrator:71748de9-149-11112 + image: quay.io/devtron/migrator:e026843e-866-11925 env: - name: SCRIPT_LOCATION value: scripts/casbin/ @@ -83,7 +85,7 @@ spec: spec: containers: - name: postgresql-migrate-gitsensor - image: quay.io/devtron/migrator:71748de9-149-11112 + image: quay.io/devtron/migrator:e026843e-866-11925 env: - name: SCRIPT_LOCATION value: scripts/sql/ @@ -121,7 +123,7 @@ spec: spec: containers: - name: postgresql-migrate-lens - image: quay.io/devtron/migrator:71748de9-149-11112 + image: quay.io/devtron/migrator:e026843e-866-11925 env: - name: SCRIPT_LOCATION value: scripts/sql/ diff --git a/tests/integrationTesting/nats-server.yaml b/tests/integrationTesting/nats-server.yaml new file mode 100644 index 0000000000..662e428ac8 --- /dev/null +++ b/tests/integrationTesting/nats-server.yaml @@ -0,0 +1,271 @@ +# Source: nats/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: devtron-nats-config + namespace: "devtroncd" + labels: + helm.sh/chart: nats-0.9.2 + + app.kubernetes.io/name: nats + app.kubernetes.io/instance: devtron-nats + app.kubernetes.io/version: "2.6.3" + app.kubernetes.io/managed-by: Helm +data: + nats.conf: | + # PID file shared with configuration reloader. + pid_file: "/var/run/nats/nats.pid" + ############### + # # + # Monitoring # + # # + ############### + http: 8222 + server_name:$POD_NAME + ################################### + # # + # NATS JetStream # + # # + ################################### + jetstream { + max_mem: 1Gi + domain: devtron-jet + } + lame_duck_duration: 120s +--- +# Source: nats/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: devtron-nats + namespace: devtroncd +spec: + ports: + - name: client + nodePort: 30236 + port: 4222 + protocol: TCP + targetPort: 4222 + - name: cluster + port: 6222 + protocol: TCP + targetPort: 6222 + - name: monitor + port: 8222 + protocol: TCP + targetPort: 8222 + - name: metrics + port: 7777 + protocol: TCP + targetPort: 7777 + - name: leafnodes + port: 7422 + protocol: TCP + targetPort: 7422 + - name: gateways + port: 7522 + protocol: TCP + targetPort: 7522 + selector: + app.kubernetes.io/instance: devtron-nats + app.kubernetes.io/name: nats + sessionAffinity: None + type: NodePort +--- +# Source: nats/templates/statefulset.yaml +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: devtron-nats + namespace: "devtroncd" + labels: + helm.sh/chart: nats-0.9.2 + + app.kubernetes.io/name: nats + app.kubernetes.io/instance: devtron-nats + app.kubernetes.io/version: "2.6.3" + app.kubernetes.io/managed-by: Helm +spec: + selector: + matchLabels: + + app.kubernetes.io/name: nats + app.kubernetes.io/instance: devtron-nats + replicas: 1 + serviceName: devtron-nats + template: + metadata: + annotations: + prometheus.io/path: /metrics + prometheus.io/port: "7777" + prometheus.io/scrape: "true" + labels: + + app.kubernetes.io/name: nats + app.kubernetes.io/instance: devtron-nats + spec: + # Common volumes for the containers. + volumes: + - name: config-volume + + configMap: + name: devtron-nats-config + + + # Local volume shared with the reloader. + - name: pid + emptyDir: {} + + ################# + # # + # TLS Volumes # + # # + ################# + + + + # Required to be able to HUP signal and apply config + # reload to the server without restarting the pod. + shareProcessNamespace: true + + ################# + # # + # NATS Server # + # # + ################# + terminationGracePeriodSeconds: 120 + containers: + - name: nats + image: quay.io/devtron/nats:2.9.3-alpine + imagePullPolicy: IfNotPresent + resources: + {} + ports: + - containerPort: 4222 + name: client + - containerPort: 7422 + name: leafnodes + - containerPort: 7522 + name: gateways + - containerPort: 6222 + name: cluster + - containerPort: 8222 + name: monitor + - containerPort: 7777 + name: metrics + + command: + - "nats-server" + - "--config" + - "/etc/nats-config/nats.conf" + + # Required to be able to define an environment variable + # that refers to other environment variables. This env var + # is later used as part of the configuration file. + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: SERVER_NAME + value: $(POD_NAME) + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: CLUSTER_ADVERTISE + value: $(POD_NAME).devtron-nats.$(POD_NAMESPACE).svc.cluster.local + volumeMounts: + - name: config-volume + mountPath: /etc/nats-config + - name: pid + mountPath: /var/run/nats + - name: data + mountPath: /tmp/nats/jetstream + + # Liveness/Readiness probes against the monitoring. + # + livenessProbe: + httpGet: + path: / + port: 8222 + initialDelaySeconds: 10 + timeoutSeconds: 5 + readinessProbe: + httpGet: + path: / + port: 8222 + initialDelaySeconds: 10 + timeoutSeconds: 5 + + # Gracefully stop NATS Server on pod deletion or image upgrade. + # + lifecycle: + preStop: + exec: + # Using the alpine based NATS image, we add an extra sleep that is + # the same amount as the terminationGracePeriodSeconds to allow + # the NATS Server to gracefully terminate the client connections. + # + command: + - "/bin/sh" + - "-c" + - "nats-server -sl=ldm=/var/run/nats/nats.pid && /bin/sleep 120" + + ################################# + # # + # NATS Configuration Reloader # + # # + ################################# + + - name: reloader + image: quay.io/devtron/nats-server-config-reloader:0.6.2 + imagePullPolicy: IfNotPresent + resources: + null + command: + - "nats-server-config-reloader" + - "-pid" + - "/var/run/nats/nats.pid" + - "-config" + - "/etc/nats-config/nats.conf" + volumeMounts: + - name: config-volume + mountPath: /etc/nats-config + - name: pid + mountPath: /var/run/nats + + + ############################## + # # + # NATS Prometheus Exporter # + # # + ############################## + + - name: metrics + image: quay.io/devtron/prometheus-nats-exporter:0.9.0 + imagePullPolicy: IfNotPresent + resources: + {} + args: + - -connz + - -routez + - -subz + - -varz + - -jsz=all + - -prefix=nats + - -use_internal_server_id + - http://localhost:8222/ + ports: + - containerPort: 7777 + name: metrics + + + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 5Gi \ No newline at end of file diff --git a/tests/integrationTesting/postgresql.yaml b/tests/integrationTesting/postgresql.yaml index 1984934afb..699d71393a 100644 --- a/tests/integrationTesting/postgresql.yaml +++ b/tests/integrationTesting/postgresql.yaml @@ -67,9 +67,10 @@ metadata: chart: postgresql-8.6.4 release: "devtron" spec: - type: ClusterIP + type: NodePort ports: - name: tcp-postgresql + nodePort: 30468 port: 5432 targetPort: tcp-postgresql selector: diff --git a/tests/integrationTesting/run-integration-test.sh b/tests/integrationTesting/run-integration-test.sh old mode 100644 new mode 100755 index 625033145d..70a791ca9b --- a/tests/integrationTesting/run-integration-test.sh +++ b/tests/integrationTesting/run-integration-test.sh @@ -1,5 +1,5 @@ -wget https://go.dev/dl/go1.18.10.linux-amd64.tar.gz -O go1.18.10.tar.gz -rm -rf /usr/local/go && tar -C /usr/local -xzf go1.18.10.tar.gz +wget https://go.dev/dl/go1.22.1.linux-amd64.tar.gz -O go1.22.1.tar.gz +rm -rf /usr/local/go && tar -C /usr/local -xzf go1.22.1.tar.gz export GOPATH='/usr/local/go' export PATH=$PATH:$GOPATH/bin #go test ./pkg/pipeline diff --git a/util/GlobalConfig.go b/util/GlobalConfig.go index 5728015bef..d82f16e2db 100644 --- a/util/GlobalConfig.go +++ b/util/GlobalConfig.go @@ -19,6 +19,7 @@ type GlobalEnvVariables struct { GitOpsRepoPrefix string `env:"GITOPS_REPO_PREFIX" envDefault:""` EnableAsyncInstallDevtronChart bool `env:"ENABLE_ASYNC_INSTALL_DEVTRON_CHART" envDefault:"false"` ExposeCiMetrics bool `env:"EXPOSE_CI_METRICS" envDefault:"false"` + ExecuteWireNilChecker bool `env:"EXECUTE_WIRE_NIL_CHECKER" envDefault:"false"` } type DevtronSecretConfig struct { diff --git a/util/rbac/EnforcerUtil.go b/util/rbac/EnforcerUtil.go index b926e77b74..0eef86135f 100644 --- a/util/rbac/EnforcerUtil.go +++ b/util/rbac/EnforcerUtil.go @@ -88,7 +88,6 @@ type EnforcerUtilImpl struct { ciPipelineRepository pipelineConfig.CiPipelineRepository clusterRepository repository.ClusterRepository enforcer casbin.Enforcer - *EnforcerUtilHelmImpl } func NewEnforcerUtilImpl(logger *zap.SugaredLogger, teamRepository team.TeamRepository, @@ -103,11 +102,7 @@ func NewEnforcerUtilImpl(logger *zap.SugaredLogger, teamRepository team.TeamRepo pipelineRepository: pipelineRepository, ciPipelineRepository: ciPipelineRepository, clusterRepository: clusterRepository, - EnforcerUtilHelmImpl: &EnforcerUtilHelmImpl{ - logger: logger, - clusterRepository: clusterRepository, - }, - enforcer: enforcer, + enforcer: enforcer, } } diff --git a/wireNilChecker.env b/wireNilChecker.env new file mode 100644 index 0000000000..5ead1a4787 --- /dev/null +++ b/wireNilChecker.env @@ -0,0 +1,44 @@ +PG_PORT=30468 +HOSTNAME=e2ecc77bfb24 +TEST_BRANCH= +SHLVL=1 +BUILD_LOG_TTL_VALUE_IN_SECS=3600 +HOME=/root +OLDPWD=/ +PG_PASSWORD=devtronpg +RUNTIME_CONFIG_LOCAL_DEV=true +ACD_PASSWORD=TJnZICwS-rmfLfl0 +PG_USER=postgres +MINIO_ACCESS_KEY=YJOczlGFxTR0k05ORsa8 +BLOB_STORAGE_S3_BUCKET_VERSIONED=true +DEVTRON_DEX_SECRET_NAMESPACE=devtroncd +MINIO_ENDPOINT=http://20.72.186.201:9000/minio/ +DIND_COMMIT=65cfcc28ab37cb75e1560e4b4738719c07c6618e +AZURE_GATEWAY_CONNECTION_INSECURE=false +CLOUD_PROVIDER=MINIO +BLOB_STORAGE_S3_ENDPOINT_INSECURE=false +TERM=xterm +ACD_URL=localhost:8000 +DEVTRON_INSTALLATION_TYPE=enterprise +DEVTRONCD_NAMESPACE=devtroncd +BLOB_STORAGE_ENABLED=true +CI_NODE_LABEL_SELECTOR=abc=dbc,bcd=def +CD_LIMIT_CI_CPU=0.5 +PG_DATABASE=orchestrator +ORCH_HOST=http://devtroncd-orchestrator-service-prod.devtroncd/webhook/msg/nats +DOCKER_VERSION=26.0.0 +DOCKER_TLS_CERTDIR=/certs +DEVTRON_DEFAULT_NAMESPACE=devtroncd +IN_APP_LOGGING_ENABLED=false +ACD_USERNAME=admin +MINIO_SECRET_KEY=VK7auS21cIeIn5icRjY3KvogEL3SqZMoeq0XL4em +PWD=/test +SCOPED_VARIABLE_ENABLED=true +EXECUTE_WIRE_NIL_CHECKER=true +TEST_BRANCH= +LATEST_HASH= +GOPATH=/usr/local/go +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin +PROXY_SERVICE_CONFIG={} + +