diff --git a/npm/pkg/controlplane/controllers/v2/namespacecontroller.go b/npm/pkg/controlplane/controllers/v2/namespaceController.go similarity index 100% rename from npm/pkg/controlplane/controllers/v2/namespacecontroller.go rename to npm/pkg/controlplane/controllers/v2/namespaceController.go diff --git a/npm/pkg/controlplane/controllers/v2/nameSpaceController_test.go b/npm/pkg/controlplane/controllers/v2/namespaceController_test.go similarity index 100% rename from npm/pkg/controlplane/controllers/v2/nameSpaceController_test.go rename to npm/pkg/controlplane/controllers/v2/namespaceController_test.go diff --git a/npm/pkg/controlplane/controllers/v2/networkPolicyController.go b/npm/pkg/controlplane/controllers/v2/networkPolicyController.go index 0227d1468e..715998339b 100644 --- a/npm/pkg/controlplane/controllers/v2/networkPolicyController.go +++ b/npm/pkg/controlplane/controllers/v2/networkPolicyController.go @@ -23,7 +23,10 @@ import ( "k8s.io/klog" ) -var errNetPolKeyFormat = errors.New("invalid network policy key format") +var ( + errNetPolKeyFormat = errors.New("invalid network policy key format") + errNetPolTranslationFailure = errors.New("failed to translate network policy") +) type NetworkPolicyController struct { netPolLister netpollister.NetworkPolicyLister @@ -255,7 +258,17 @@ func (c *NetworkPolicyController) syncAddAndUpdateNetPol(netPolObj *networkingv1 } // install translated rules into kernel - npmNetPolObj := translation.TranslatePolicy(netPolObj) + npmNetPolObj, err := translation.TranslatePolicy(netPolObj) + if err != nil { + if errors.Is(err, translation.ErrUnsupportedNamedPort) || errors.Is(err, translation.ErrUnsupportedNegativeMatch) { + // We can safely suppress unsupported network policy because re-Queuing will result in same error + klog.Warningf("NetworkPolicy %s in namespace %s is not translated because it has unsupported translated features of Windows.", netPolObj.ObjectMeta.Name, netPolObj.ObjectMeta.Namespace) + return nil + } + klog.Errorf("Failed to translate podSelector in NetworkPolicy %s in namespace %s: %s", netPolObj.ObjectMeta.Name, netPolObj.ObjectMeta.Namespace, err.Error()) + return errNetPolTranslationFailure + } + // install translated rules into Dataplane // DP update policy call will check if this policy already exists in kernel // if yes: then will delete old rules and program new rules diff --git a/npm/pkg/controlplane/controllers/v2/podcontroller.go b/npm/pkg/controlplane/controllers/v2/podController.go similarity index 99% rename from npm/pkg/controlplane/controllers/v2/podcontroller.go rename to npm/pkg/controlplane/controllers/v2/podController.go index 233fd8e279..4773728ba9 100644 --- a/npm/pkg/controlplane/controllers/v2/podcontroller.go +++ b/npm/pkg/controlplane/controllers/v2/podController.go @@ -603,6 +603,10 @@ func (c *PodController) cleanUpDeletedPod(cachedNpmPodKey string) error { // manageNamedPortIpsets helps with adding or deleting Pod namedPort IPsets. func (c *PodController) manageNamedPortIpsets(portList []corev1.ContainerPort, podKey, podIP, nodeName string, namedPortOperation NamedPortOperation) error { + if util.IsWindowsDP() { + klog.Warningf("Windows Dataplane does not support NamedPort operations. Operation: %s portList is %+v", namedPortOperation, portList) + return nil + } for _, port := range portList { klog.Infof("port is %+v", port) if port.Name == "" { diff --git a/npm/pkg/controlplane/translation/parseSelector.go b/npm/pkg/controlplane/translation/parseSelector.go index 0bbc1cfe0d..6d9aca7ced 100644 --- a/npm/pkg/controlplane/translation/parseSelector.go +++ b/npm/pkg/controlplane/translation/parseSelector.go @@ -230,7 +230,7 @@ func parseNSSelector(selector *metav1.LabelSelector) []labelSelector { // parsePodSelector parses podSelector and returns slice of labelSelector object // which includes operator, setType, ipset name and its members slice. // Members slice exists only if setType is only NestedLabelOfPod. -func parsePodSelector(selector *metav1.LabelSelector) []labelSelector { +func parsePodSelector(selector *metav1.LabelSelector) ([]labelSelector, error) { parsedSelectors := newParsedSelectors() // #1. MatchLabels @@ -245,7 +245,11 @@ func parsePodSelector(selector *metav1.LabelSelector) []labelSelector { var setName string var setType ipsets.SetType var members []string - switch op := req.Operator; op { + op := req.Operator + if unsupportedOpsInWindows(op) { + return nil, ErrUnsupportedNegativeMatch + } + switch op { case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn: // "(!) + matchKey + : + matchVal" case if len(req.Values) == 1 { @@ -270,5 +274,10 @@ func parsePodSelector(selector *metav1.LabelSelector) []labelSelector { parsedSelectors.addSelector(noNegativeOp, setType, setName, members...) } - return parsedSelectors.labelSelectors + return parsedSelectors.labelSelectors, nil +} + +func unsupportedOpsInWindows(op metav1.LabelSelectorOperator) bool { + return util.IsWindowsDP() && + (op == metav1.LabelSelectorOpNotIn || op == metav1.LabelSelectorOpDoesNotExist) } diff --git a/npm/pkg/controlplane/translation/translatePolicy.go b/npm/pkg/controlplane/translation/translatePolicy.go index 424e74807f..fbb2516d95 100644 --- a/npm/pkg/controlplane/translation/translatePolicy.go +++ b/npm/pkg/controlplane/translation/translatePolicy.go @@ -9,7 +9,7 @@ import ( "github.com/Azure/azure-container-networking/npm/util" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/klog" + "k8s.io/klog/v2" ) /* @@ -19,7 +19,13 @@ TODO(jungukcho) (https://kubernetes.io/docs/concepts/services-networking/network-policies/#targeting-a-namespace-by-its-name) */ -var errUnknownPortType = errors.New("unknown port Type") +var ( + errUnknownPortType = errors.New("unknown port Type") + // ErrUnsupportedNamedPort is returned when named port translation feature is used in windows. + ErrUnsupportedNamedPort = errors.New("unsupported namedport translation features used on windows") + // ErrUnsupportedNegativeMatch is returned when negative match translation feature is used in windows. + ErrUnsupportedNegativeMatch = errors.New("unsupported NotExist operator translation features used on windows") +) type netpolPortType string @@ -28,8 +34,6 @@ const ( namedPortType netpolPortType = "namedport" included bool = true ipBlocksetNameFormat = "%s-in-ns-%s-%d%s" - onlyKeyLabel = 1 - keyValueLabel = 2 ) // portType returns type of ports (e.g., numeric port or namedPort) given NetworkPolicyPort object. @@ -37,6 +41,10 @@ func portType(portRule networkingv1.NetworkPolicyPort) (netpolPortType, error) { if portRule.Port == nil || portRule.Port.IntValue() != 0 { return numericPortType, nil } else if portRule.Port.IntValue() == 0 && portRule.Port.String() != "" { + if util.IsWindowsDP() { + klog.Warningf("Windows does not support named port. Use numeric port instead.") + return "", ErrUnsupportedNamedPort + } return namedPortType, nil } // TODO (jungukcho): check whether this can be possible or not. @@ -208,8 +216,11 @@ func ipBlockRule(policyName, ns string, direction policies.Direction, matchType // PodSelector translates podSelector of NetworkPolicyPeer field in networkpolicy object to translatedIPSet and SetInfo. // This function is called only when the NetworkPolicyPeer has namespaceSelector field. -func podSelector(matchType policies.MatchType, selector *metav1.LabelSelector) ([]*ipsets.TranslatedIPSet, []policies.SetInfo) { - podSelectors := parsePodSelector(selector) +func podSelector(matchType policies.MatchType, selector *metav1.LabelSelector) ([]*ipsets.TranslatedIPSet, []policies.SetInfo, error) { + podSelectors, err := parsePodSelector(selector) + if err != nil { + return nil, nil, err + } lenOfPodSelectors := len(podSelectors) podSelectorIPSets := []*ipsets.TranslatedIPSet{} podSelectorList := make([]policies.SetInfo, lenOfPodSelectors) @@ -225,18 +236,20 @@ func podSelector(matchType policies.MatchType, selector *metav1.LabelSelector) ( podSelectorList[i] = policies.NewSetInfo(ps.setName, ps.setType, ps.include, matchType) } - return podSelectorIPSets, podSelectorList + return podSelectorIPSets, podSelectorList, nil } // podSelectorWithNS translates podSelector of spec and NetworkPolicyPeer in networkpolicy object to translatedIPSet and SetInfo. // This function is called only when the NetworkPolicyPeer does not have namespaceSelector field. -func podSelectorWithNS(ns string, matchType policies.MatchType, selector *metav1.LabelSelector) ([]*ipsets.TranslatedIPSet, []policies.SetInfo) { - podSelectorIPSets, podSelectorList := podSelector(matchType, selector) - +func podSelectorWithNS(ns string, matchType policies.MatchType, selector *metav1.LabelSelector) ([]*ipsets.TranslatedIPSet, []policies.SetInfo, error) { + podSelectorIPSets, podSelectorList, err := podSelector(matchType, selector) + if err != nil { + return nil, nil, err + } // Add translatedIPSet and SetInfo based on namespace podSelectorIPSets = append(podSelectorIPSets, ipsets.NewTranslatedIPSet(ns, ipsets.Namespace)) podSelectorList = append(podSelectorList, policies.NewSetInfo(ns, ipsets.Namespace, included, matchType)) - return podSelectorIPSets, podSelectorList + return podSelectorIPSets, podSelectorList, nil } // nameSpaceSelector translates namespaceSelector of NetworkPolicyPeer in networkpolicy object to translatedIPSet and SetInfo. @@ -289,20 +302,18 @@ func ruleExists(ports []networkingv1.NetworkPolicyPort, peer []networkingv1.Netw // peerAndPortRule deals with composite rules including ports and peers // (e.g., IPBlock, podSelector, namespaceSelector, or both podSelector and namespaceSelector). -func peerAndPortRule(npmNetPol *policies.NPMNetworkPolicy, direction policies.Direction, ports []networkingv1.NetworkPolicyPort, setInfo []policies.SetInfo) { +func peerAndPortRule(npmNetPol *policies.NPMNetworkPolicy, direction policies.Direction, ports []networkingv1.NetworkPolicyPort, setInfo []policies.SetInfo) error { if len(ports) == 0 { acl := policies.NewACLPolicy(npmNetPol.NameSpace, npmNetPol.Name, policies.Allowed, direction) acl.AddSetInfo(setInfo) npmNetPol.ACLs = append(npmNetPol.ACLs, acl) - return + return nil } for i := range ports { portKind, err := portType(ports[i]) if err != nil { - // TODO(jungukcho): handle error - klog.Infof("Invalid NetworkPolicyPort %s", err) - continue + return err } acl := policies.NewACLPolicy(npmNetPol.NameSpace, npmNetPol.Name, policies.Allowed, direction) @@ -310,11 +321,12 @@ func peerAndPortRule(npmNetPol *policies.NPMNetworkPolicy, direction policies.Di npmNetPol.RuleIPSets = portRule(npmNetPol.RuleIPSets, acl, &ports[i], portKind) npmNetPol.ACLs = append(npmNetPol.ACLs, acl) } + return nil } // translateRule translates ingress or egress rules and update npmNetPol object. func translateRule(npmNetPol *policies.NPMNetworkPolicy, direction policies.Direction, matchType policies.MatchType, ruleIndex int, - ports []networkingv1.NetworkPolicyPort, peers []networkingv1.NetworkPolicyPeer) { + ports []networkingv1.NetworkPolicyPort, peers []networkingv1.NetworkPolicyPeer) error { // TODO(jungukcho): need to clean up it. // Leave allowExternal variable now while the condition is checked before calling this function. allowExternal, portRuleExists, peerRuleExists := ruleExists(ports, peers) @@ -328,7 +340,7 @@ func translateRule(npmNetPol *policies.NPMNetworkPolicy, direction policies.Dire npmNetPol.RuleIPSets = append(npmNetPol.RuleIPSets, ruleIPSets) acl.AddSetInfo([]policies.SetInfo{allowAllInternalSetInfo}) npmNetPol.ACLs = append(npmNetPol.ACLs, acl) - return + return nil } // #1. Only Ports fields exist in rule @@ -336,8 +348,7 @@ func translateRule(npmNetPol *policies.NPMNetworkPolicy, direction policies.Dire for i := range ports { portKind, err := portType(ports[i]) if err != nil { - klog.Infof("Invalid NetworkPolicyPort %s", err) - continue + return err } portACL := policies.NewACLPolicy(npmNetPol.NameSpace, npmNetPol.Name, policies.Allowed, direction) @@ -357,7 +368,10 @@ func translateRule(npmNetPol *policies.NPMNetworkPolicy, direction policies.Dire if j != 0 { continue } - peerAndPortRule(npmNetPol, direction, ports, []policies.SetInfo{ipBlockSetInfo}) + err := peerAndPortRule(npmNetPol, direction, ports, []policies.SetInfo{ipBlockSetInfo}) + if err != nil { + return err + } } // Do not need to run below code to translate PodSelector and NamespaceSelector // since IPBlock field is exclusive in NetworkPolicyPeer (i.e., peer in this code). @@ -377,16 +391,25 @@ func translateRule(npmNetPol *policies.NPMNetworkPolicy, direction policies.Dire for i := range flattenNSSelector { nsSelectorIPSets, nsSelectorList := nameSpaceSelector(matchType, &flattenNSSelector[i]) npmNetPol.RuleIPSets = append(npmNetPol.RuleIPSets, nsSelectorIPSets...) - peerAndPortRule(npmNetPol, direction, ports, nsSelectorList) + err := peerAndPortRule(npmNetPol, direction, ports, nsSelectorList) + if err != nil { + return err + } } continue } // #2.3 handle podSelector and port if exist if peer.PodSelector != nil && peer.NamespaceSelector == nil { - podSelectorIPSets, podSelectorList := podSelectorWithNS(npmNetPol.NameSpace, matchType, peer.PodSelector) + podSelectorIPSets, podSelectorList, err := podSelectorWithNS(npmNetPol.NameSpace, matchType, peer.PodSelector) + if err != nil { + return err + } npmNetPol.RuleIPSets = append(npmNetPol.RuleIPSets, podSelectorIPSets...) - peerAndPortRule(npmNetPol, direction, ports, podSelectorList) + err = peerAndPortRule(npmNetPol, direction, ports, podSelectorList) + if err != nil { + return err + } continue } @@ -399,7 +422,10 @@ func translateRule(npmNetPol *policies.NPMNetworkPolicy, direction policies.Dire } // #2.4 handle namespaceSelector and podSelector and port if exist - podSelectorIPSets, podSelectorList := podSelector(matchType, peer.PodSelector) + podSelectorIPSets, podSelectorList, err := podSelector(matchType, peer.PodSelector) + if err != nil { + return err + } npmNetPol.RuleIPSets = append(npmNetPol.RuleIPSets, podSelectorIPSets...) // Before translating NamespaceSelector, flattenNameSpaceSelector function call should be called @@ -409,9 +435,13 @@ func translateRule(npmNetPol *policies.NPMNetworkPolicy, direction policies.Dire nsSelectorIPSets, nsSelectorList := nameSpaceSelector(matchType, &flattenNSSelector[i]) npmNetPol.RuleIPSets = append(npmNetPol.RuleIPSets, nsSelectorIPSets...) nsSelectorList = append(nsSelectorList, podSelectorList...) - peerAndPortRule(npmNetPol, direction, ports, nsSelectorList) + err := peerAndPortRule(npmNetPol, direction, ports, nsSelectorList) + if err != nil { + return err + } } } + return nil } // defaultDropACL returns ACLPolicy to drop traffic which is not allowed. @@ -436,12 +466,12 @@ func isAllowAllToIngress(ingress []networkingv1.NetworkPolicyIngressRule) bool { // ingressPolicy traslates NetworkPolicyIngressRule in NetworkPolicy object // to NPMNetworkPolicy object. -func ingressPolicy(npmNetPol *policies.NPMNetworkPolicy, ingress []networkingv1.NetworkPolicyIngressRule) { +func ingressPolicy(npmNetPol *policies.NPMNetworkPolicy, ingress []networkingv1.NetworkPolicyIngressRule) error { // #1. Allow all traffic from both internal and external. // In yaml file, it is specified with '{}'. if isAllowAllToIngress(ingress) { allowAllPolicy(npmNetPol, policies.Ingress) - return + return nil } // #2. If ingress is nil (in yaml file, it is specified with '[]'), it means "Deny all" - it does not allow receiving any traffic from others. @@ -449,17 +479,20 @@ func ingressPolicy(npmNetPol *policies.NPMNetworkPolicy, ingress []networkingv1. // Except for allow all traffic case in #1, the rest of them should have default drop rules. dropACL := defaultDropACL(npmNetPol.NameSpace, npmNetPol.Name, policies.Ingress) npmNetPol.ACLs = append(npmNetPol.ACLs, dropACL) - return + return nil } // #3. Ingress rule is not AllowAll (including internal and external) and DenyAll policy. // So, start translating ingress policy. for i, rule := range ingress { - translateRule(npmNetPol, policies.Ingress, policies.SrcMatch, i, rule.Ports, rule.From) + if err := translateRule(npmNetPol, policies.Ingress, policies.SrcMatch, i, rule.Ports, rule.From); err != nil { + return err + } } // Except for allow all traffic case in #1, the rest of them should have default drop rules. dropACL := defaultDropACL(npmNetPol.NameSpace, npmNetPol.Name, policies.Ingress) npmNetPol.ACLs = append(npmNetPol.ACLs, dropACL) + return nil } // isAllowAllToEgress returns true if this network policy allows all traffic to internal (i.e,. K8s cluster) and external (i.e., internet) @@ -472,12 +505,12 @@ func isAllowAllToEgress(egress []networkingv1.NetworkPolicyEgressRule) bool { // egressPolicy traslates NetworkPolicyEgressRule in networkpolicy object // to NPMNetworkPolicy object. -func egressPolicy(npmNetPol *policies.NPMNetworkPolicy, egress []networkingv1.NetworkPolicyEgressRule) { +func egressPolicy(npmNetPol *policies.NPMNetworkPolicy, egress []networkingv1.NetworkPolicyEgressRule) error { // #1. Allow all traffic to both internal and external. // In yaml file, it is specified with '{}'. if isAllowAllToEgress(egress) { allowAllPolicy(npmNetPol, policies.Egress) - return + return nil } // #2. If egress is nil (in yaml file, it is specified with '[]'), it means "Deny all" - it does not allow sending traffic to others. @@ -485,41 +518,55 @@ func egressPolicy(npmNetPol *policies.NPMNetworkPolicy, egress []networkingv1.Ne // Except for allow all traffic case in #1, the rest of them should have default drop rules. dropACL := defaultDropACL(npmNetPol.NameSpace, npmNetPol.Name, policies.Egress) npmNetPol.ACLs = append(npmNetPol.ACLs, dropACL) - return + return nil } // #3. Egress rule is not AllowAll (including internal and external) and DenyAll. // So, start translating egress policy. for i, rule := range egress { - translateRule(npmNetPol, policies.Egress, policies.DstMatch, i, rule.Ports, rule.To) + err := translateRule(npmNetPol, policies.Egress, policies.DstMatch, i, rule.Ports, rule.To) + if err != nil { + return err + } } // #3. Except for allow all traffic case in #1, the rest of them should have default drop rules. // Add drop ACL to drop the rest of traffic which is not specified in Egress Spec. dropACL := defaultDropACL(npmNetPol.NameSpace, npmNetPol.Name, policies.Egress) npmNetPol.ACLs = append(npmNetPol.ACLs, dropACL) + return nil } // TranslatePolicy traslates networkpolicy object to NPMNetworkPolicy object // and return the NPMNetworkPolicy object. -func TranslatePolicy(npObj *networkingv1.NetworkPolicy) *policies.NPMNetworkPolicy { +func TranslatePolicy(npObj *networkingv1.NetworkPolicy) (*policies.NPMNetworkPolicy, error) { npmNetPol := policies.NewNPMNetworkPolicy(npObj.Name, npObj.Namespace) // podSelector in spec.PodSelector is common for ingress and egress. // Process this podSelector first. - npmNetPol.PodSelectorIPSets, npmNetPol.PodSelectorList = podSelectorWithNS(npmNetPol.NameSpace, policies.EitherMatch, &npObj.Spec.PodSelector) + var err error + npmNetPol.PodSelectorIPSets, npmNetPol.PodSelectorList, err = podSelectorWithNS(npmNetPol.NameSpace, policies.EitherMatch, &npObj.Spec.PodSelector) + if err != nil { + return nil, err + } // Each NetworkPolicy includes a policyTypes list which may include either Ingress, Egress, or both. // If no policyTypes are specified on a NetworkPolicy then by default Ingress will always be set // and Egress will be set if the NetworkPolicy has any egress rules. for _, ptype := range npObj.Spec.PolicyTypes { if ptype == networkingv1.PolicyTypeIngress { - ingressPolicy(npmNetPol, npObj.Spec.Ingress) + err := ingressPolicy(npmNetPol, npObj.Spec.Ingress) + if err != nil { + return nil, err + } } else { - egressPolicy(npmNetPol, npObj.Spec.Egress) + err := egressPolicy(npmNetPol, npObj.Spec.Egress) + if err != nil { + return nil, err + } } } klog.Infof("JUST-TRANSLATED-THIS-POLICY:\n%s", npmNetPol.String()) klog.Infof("THIS-NPOBJ:\n%+v", npObj) - return npmNetPol + return npmNetPol, nil } diff --git a/npm/pkg/controlplane/translation/translatePolicy_test.go b/npm/pkg/controlplane/translation/translatePolicy_test.go index 59188cc05d..e5d46d1a58 100644 --- a/npm/pkg/controlplane/translation/translatePolicy_test.go +++ b/npm/pkg/controlplane/translation/translatePolicy_test.go @@ -699,11 +699,13 @@ func TestPodSelector(t *testing.T) { t.Parallel() var podSelectorIPSets []*ipsets.TranslatedIPSet var podSelectorList []policies.SetInfo + var err error if tt.namespace == "" { - podSelectorIPSets, podSelectorList = podSelector(tt.matchType, tt.labelSelector) + podSelectorIPSets, podSelectorList, err = podSelector(tt.matchType, tt.labelSelector) } else { - podSelectorIPSets, podSelectorList = podSelectorWithNS(tt.namespace, tt.matchType, tt.labelSelector) + podSelectorIPSets, podSelectorList, err = podSelectorWithNS(tt.namespace, tt.matchType, tt.labelSelector) } + require.NoError(t, err) require.Equal(t, tt.podSelectorIPSets, podSelectorIPSets) require.Equal(t, tt.podSelectorList, podSelectorList) }) @@ -1275,7 +1277,8 @@ func TestPeerAndPortRule(t *testing.T) { Name: tt.npmNetPol.Name, NameSpace: tt.npmNetPol.NameSpace, } - peerAndPortRule(npmNetPol, policies.Ingress, tt.ports, setInfo) + err := peerAndPortRule(npmNetPol, policies.Ingress, tt.ports, setInfo) + require.NoError(t, err) require.Equal(t, tt.npmNetPol, npmNetPol) }) } @@ -1486,8 +1489,11 @@ func TestIngressPolicy(t *testing.T) { Name: tt.npmNetPol.Name, NameSpace: tt.npmNetPol.NameSpace, } - npmNetPol.PodSelectorIPSets, npmNetPol.PodSelectorList = podSelectorWithNS(npmNetPol.NameSpace, policies.EitherMatch, tt.targetSelector) - ingressPolicy(npmNetPol, tt.rules) + var err error + npmNetPol.PodSelectorIPSets, npmNetPol.PodSelectorList, err = podSelectorWithNS(npmNetPol.NameSpace, policies.EitherMatch, tt.targetSelector) + require.NoError(t, err) + err = ingressPolicy(npmNetPol, tt.rules) + require.NoError(t, err) require.Equal(t, tt.npmNetPol, npmNetPol) }) } diff --git a/npm/util/util.go b/npm/util/util.go index 696402e863..a1898e29ce 100644 --- a/npm/util/util.go +++ b/npm/util/util.go @@ -7,6 +7,7 @@ import ( "hash/fnv" "os" "regexp" + "runtime" "sort" "strconv" "strings" @@ -23,6 +24,10 @@ var IsNewNwPolicyVerFlag = false // regex to get minor version var re = regexp.MustCompile("[0-9]+") +func IsWindowsDP() bool { + return runtime.GOOS == "windows" +} + // Exists reports whether the named file or directory exists. func Exists(filePath string) bool { if _, err := os.Stat(filePath); err == nil {