Repository: openyurtio/openyurt (go)

Issue:
[feature request] Do not post coverage rate to Codecov when it is not `openyurtio/openyurt`
<!-- Please only use this template for submitting feature requests -->

**What would you like to be added**:

We should not post coverage rate to Codecov when we run CI in our forked repo. It always results in failure.

**Why is this needed**:

Sometimes we may trigger the CI in our forked repos to figure out if our prs can pass the CI. But in the forked repo, CI always tries to post the unit test coverage rate to the Codecov site which will fail.


**others**
/kind feature


Pull request:
post result to codecov only in openyurt repo
<!--  Thanks for sending a pull request!  Here are some tips for you:
https://github.com/openyurtio/openyurt/blob/master/CONTRIBUTING.md 
-->

#### What type of PR is this?
/kind enhancement

#### What this PR does / why we need it:

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #946 

#### Special notes for your reviewer:
<!--
use this label to assign your reviewer
/assign @your_reviewer
-->

#### Does this PR introduce a user-facing change?
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

-->
```release-note

```

#### other Note
<!--
If your current PR is still working in process, start the PR title name with [WIP], such as: [WIP] add new crd for yurt-app-manager
If the PR title name begins with [WIP], OpenYurt-bot automatically adds a do-not-merge/work-in-progress label for your pr 
-->
refer to https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsif


Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 98fe6df8..c7da3aba 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -93,6 +93,8 @@ jobs:
       - name: Run Unit Tests
         run: make test
       - name: Publish Unit Test Coverage
+        # only publish result in openyurt repo
+        if: github.repository == 'openyurtio/openyurt'
         uses: codecov/codecov-action@v3
         with:
           name: codecov-umbrella
diff --git a/config/setup/yurt-tunnel-dns.yaml b/config/setup/yurt-tunnel-dns.yaml
new file mode 100644
index 00000000..19b5a863
--- /dev/null
+++ b/config/setup/yurt-tunnel-dns.yaml
@@ -0,0 +1,159 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: yurt-tunnel-dns
+  namespace: kube-system
+data:
+  Corefile: |
+    .:53 {
+        errors
+        health {
+          lameduck 5s
+        }
+        ready
+        hosts /etc/edge/tunnel-nodes {
+            reload 300ms
+            fallthrough
+        }
+        prometheus :9153
+        forward . /etc/resolv.conf {
+          max_concurrent 1000
+        }
+        cache 30
+        loop
+        reload
+        loadbalance
+    }
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: yurt-tunnel-nodes
+  namespace: kube-system
+data:
+  tunnel-nodes: |
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: yurt-tunnel-dns
+  namespace: kube-system
+  labels:
+    k8s-app: yurt-tunnel-dns
+    kubernetes.io/name: "CoreDNS"
+spec:
+  replicas: 1
+  strategy:
+    type: RollingUpdate
+    rollingUpdate:
+      maxUnavailable: 1
+  selector:
+    matchLabels:
+      k8s-app: yurt-tunnel-dns
+  template:
+    metadata:
+      labels:
+        k8s-app: yurt-tunnel-dns
+    spec:
+      priorityClassName: system-cluster-critical
+      tolerations:
+        - operator: "Exists"
+      nodeSelector:
+        kubernetes.io/os: linux
+        openyurt.io/is-edge-worker: "false"
+      affinity:
+        podAntiAffinity:
+          requiredDuringSchedulingIgnoredDuringExecution:
+            - labelSelector:
+                matchExpressions:
+                  - key: k8s-app
+                    operator: In
+                    values: ["yurt-tunnel-dns"]
+              topologyKey: kubernetes.io/hostname
+      containers:
+        - name: yurt-tunnel-dns
+          image: coredns/coredns:1.9.3
+          imagePullPolicy: IfNotPresent
+          resources:
+            limits:
+              memory: 170Mi
+            requests:
+              cpu: 100m
+              memory: 70Mi
+          args: [ "-conf", "/etc/yurt-tunnel-dns/Corefile" ]
+          volumeMounts:
+            - name: config-volume
+              mountPath: /etc/yurt-tunnel-dns
+              readOnly: true
+            - name: hosts
+              mountPath: /etc/edge
+              readOnly: true
+          ports:
+            - containerPort: 53
+              name: dns
+              protocol: UDP
+            - containerPort: 53
+              name: dns-tcp
+              protocol: TCP
+            - containerPort: 9153
+              name: metrics
+              protocol: TCP
+          securityContext:
+            allowPrivilegeEscalation: false
+            capabilities:
+              add:
+                - NET_BIND_SERVICE
+              drop:
+                - all
+            readOnlyRootFilesystem: true
+          livenessProbe:
+            httpGet:
+              path: /health
+              port: 8080
+              scheme: HTTP
+            initialDelaySeconds: 60
+            timeoutSeconds: 5
+            successThreshold: 1
+            failureThreshold: 5
+          readinessProbe:
+            httpGet:
+              path: /ready
+              port: 8181
+              scheme: HTTP
+      dnsPolicy: Default
+      volumes:
+        - name: config-volume
+          configMap:
+            name: yurt-tunnel-dns
+            items:
+              - key: Corefile
+                path: Corefile
+        - name: hosts
+          configMap:
+            name: yurt-tunnel-nodes
+            defaultMode: 420
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: yurt-tunnel-dns
+  namespace: kube-system
+  annotations:
+    prometheus.io/port: "9153"
+    prometheus.io/scrape: "true"
+  labels:
+    k8s-app: yurt-tunnel-dns
+spec:
+  selector:
+    k8s-app: yurt-tunnel-dns
+  type: ClusterIP
+  ports:
+    - name: dns
+      port: 53
+      protocol: UDP
+    - name: dns-tcp
+      port: 53
+      protocol: TCP
+    - name: metrics
+      port: 9153
+      protocol: TCP
diff --git a/pkg/yurtadm/cmd/join/join.go b/pkg/yurtadm/cmd/join/join.go
index 1092f1ab..17f81ffd 100644
--- a/pkg/yurtadm/cmd/join/join.go
+++ b/pkg/yurtadm/cmd/join/join.go
@@ -20,7 +20,6 @@ package join
 import (
 	"fmt"
 	"io"
-	"os"
 	"strings"
 
 	"github.com/lithammer/dedent"
@@ -40,6 +39,7 @@ import (
 	"github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/joindata"
 	yurtphase "github.com/openyurtio/openyurt/pkg/yurtadm/cmd/join/phases"
 	yurtconstants "github.com/openyurtio/openyurt/pkg/yurtadm/constants"
+	"github.com/openyurtio/openyurt/pkg/yurtadm/util/edgenode"
 	yurtadmutil "github.com/openyurtio/openyurt/pkg/yurtadm/util/kubernetes"
 )
 
@@ -217,16 +217,11 @@ func newJoinData(cmd *cobra.Command, args []string, opt *joinOptions, out io.Wri
 		ignoreErrors.Insert(opt.ignorePreflightErrors[i])
 	}
 
-	// Either use the config file if specified, or convert public kubeadm API to the internal JoinConfiguration
-	// and validates JoinConfiguration
-	name := opt.nodeName
-	if name == "" {
-		klog.V(1).Infoln("[preflight] found NodeName empty; using OS hostname as NodeName")
-		hostname, err := os.Hostname()
-		if err != nil {
-			return nil, err
-		}
-		name = hostname
+	// Either use specified nodename or get hostname from OS envs
+	name, err := edgenode.GetHostname(opt.nodeName)
+	if err != nil {
+		klog.Errorf("failed to get node name, %v", err)
+		return nil, err
 	}
 
 	data := &joinData{
diff --git a/pkg/yurtadm/util/edgenode/util.go b/pkg/yurtadm/util/edgenode/util.go
index e785fb05..31d25c06 100644
--- a/pkg/yurtadm/util/edgenode/util.go
+++ b/pkg/yurtadm/util/edgenode/util.go
@@ -150,6 +150,30 @@ func GetNodeName(kubeadmConfPath string) (string, error) {
 	return nodeName, nil
 }
 
+// GetHostname returns OS's hostname if 'hostnameOverride' is empty; otherwise, return 'hostnameOverride'.
+// This is compatible with kubelet's hostname processing logic
+// https://github.com/kubernetes/kubernetes/blob/c00975370a5bf81328dc56396ee05edc7306e238/pkg/util/node/node.go#L46
+var osHostName func() (string, error) = os.Hostname
+
+func GetHostname(hostnameOverride string) (string, error) {
+	hostName := hostnameOverride
+	if len(hostName) == 0 {
+		nodeName, err := osHostName()
+		if err != nil {
+			return "", fmt.Errorf("couldn't determine hostname: %v", err)
+		}
+		hostName = nodeName
+	}
+
+	// Trim whitespaces first to avoid getting an empty hostname
+	// For linux, the hostname is read from file /proc/sys/kernel/hostname directly
+	hostName = strings.TrimSpace(hostName)
+	if len(hostName) == 0 {
+		return "", fmt.Errorf("empty hostname is invalid")
+	}
+	return strings.ToLower(hostName), nil
+}
+
 // GenClientSet generates the clientset based on command option, environment variable,
 // file in $HOME/.kube or the default kubeconfig file
 func GenClientSet(flags *pflag.FlagSet) (*kubernetes.Clientset, error) {


Test patch:
diff --git a/pkg/yurtadm/util/edgenode/util_test.go b/pkg/yurtadm/util/edgenode/util_test.go
index 13bdb0fe..2719611d 100644
--- a/pkg/yurtadm/util/edgenode/util_test.go
+++ b/pkg/yurtadm/util/edgenode/util_test.go
@@ -27,3 +27,71 @@ func Test_GetPodManifestPath(t *testing.T) {
 		t.Fatal("get path err: " + path)
 	}
 }
+
+func Test_GetHostName(t *testing.T) {
+	oldOSHost := osHostName
+	defer func() {
+		osHostName = oldOSHost
+	}()
+
+	osHostName = func() (string, error) {
+		return "test_host", nil
+	}
+
+	tests := []struct {
+		name             string
+		hostNameOverride string
+		expectedHostName string
+		expectError      bool
+	}{
+		{
+			"host name with upper case character",
+			"TEST_HOST",
+			"test_host",
+			false,
+		},
+		{
+			"host name with leading space ",
+			"    test_host",
+			"test_host",
+			false,
+		},
+		{
+			"valid host name",
+			"test_host",
+			"test_host",
+			false,
+		},
+		{
+			"invalid host name",
+			"    ",
+			"",
+			true,
+		},
+		{
+			"get from os envs",
+			"",
+			"test_host",
+			false,
+		},
+	}
+
+	for _, test := range tests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			t.Logf("\tTestCase: %s", test.name)
+			{
+				hostName, err := GetHostname(test.hostNameOverride)
+				if err != nil && !test.expectError {
+					t.Errorf("unexpected error: %s", err)
+				}
+				if err == nil && test.expectError {
+					t.Errorf("expected error, got none")
+				}
+				if test.expectedHostName != hostName {
+					t.Errorf("expected output %q, got %q", test.expectedHostName, hostName)
+				}
+			}
+		})
+	}
+}