| // Copyright 2018, OpenCensus Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Package resource provides functionality for resource, which capture |
| // identifying information about the entities for which signals are exported. |
| package resource |
| |
| import ( |
| "context" |
| "fmt" |
| "os" |
| "regexp" |
| "sort" |
| "strconv" |
| "strings" |
| ) |
| |
| // Environment variables used by FromEnv to decode a resource. |
| const ( |
| EnvVarType = "OC_RESOURCE_TYPE" |
| EnvVarLabels = "OC_RESOURCE_LABELS" |
| ) |
| |
| // Resource describes an entity about which identifying information and metadata is exposed. |
| // For example, a type "k8s.io/container" may hold labels describing the pod name and namespace. |
| type Resource struct { |
| Type string |
| Labels map[string]string |
| } |
| |
| // EncodeLabels encodes a labels map to a string as provided via the OC_RESOURCE_LABELS environment variable. |
| func EncodeLabels(labels map[string]string) string { |
| sortedKeys := make([]string, 0, len(labels)) |
| for k := range labels { |
| sortedKeys = append(sortedKeys, k) |
| } |
| sort.Strings(sortedKeys) |
| |
| s := "" |
| for i, k := range sortedKeys { |
| if i > 0 { |
| s += "," |
| } |
| s += k + "=" + strconv.Quote(labels[k]) |
| } |
| return s |
| } |
| |
| var labelRegex = regexp.MustCompile(`^\s*([[:ascii:]]{1,256}?)=("[[:ascii:]]{0,256}?")\s*,`) |
| |
| // DecodeLabels decodes a serialized label map as used in the OC_RESOURCE_LABELS variable. |
| // A list of labels of the form `<key1>="<value1>",<key2>="<value2>",...` is accepted. |
| // Domain names and paths are accepted as label keys. |
| // Most users will want to use FromEnv instead. |
| func DecodeLabels(s string) (map[string]string, error) { |
| m := map[string]string{} |
| // Ensure a trailing comma, which allows us to keep the regex simpler |
| s = strings.TrimRight(strings.TrimSpace(s), ",") + "," |
| |
| for len(s) > 0 { |
| match := labelRegex.FindStringSubmatch(s) |
| if len(match) == 0 { |
| return nil, fmt.Errorf("invalid label formatting, remainder: %s", s) |
| } |
| v := match[2] |
| if v == "" { |
| v = match[3] |
| } else { |
| var err error |
| if v, err = strconv.Unquote(v); err != nil { |
| return nil, fmt.Errorf("invalid label formatting, remainder: %s, err: %s", s, err) |
| } |
| } |
| m[match[1]] = v |
| |
| s = s[len(match[0]):] |
| } |
| return m, nil |
| } |
| |
| // FromEnv is a detector that loads resource information from the OC_RESOURCE_TYPE |
| // and OC_RESOURCE_labelS environment variables. |
| func FromEnv(context.Context) (*Resource, error) { |
| res := &Resource{ |
| Type: strings.TrimSpace(os.Getenv(EnvVarType)), |
| } |
| labels := strings.TrimSpace(os.Getenv(EnvVarLabels)) |
| if labels == "" { |
| return res, nil |
| } |
| var err error |
| if res.Labels, err = DecodeLabels(labels); err != nil { |
| return nil, err |
| } |
| return res, nil |
| } |
| |
| var _ Detector = FromEnv |
| |
| // merge resource information from b into a. In case of a collision, a takes precedence. |
| func merge(a, b *Resource) *Resource { |
| if a == nil { |
| return b |
| } |
| if b == nil { |
| return a |
| } |
| res := &Resource{ |
| Type: a.Type, |
| Labels: map[string]string{}, |
| } |
| if res.Type == "" { |
| res.Type = b.Type |
| } |
| for k, v := range b.Labels { |
| res.Labels[k] = v |
| } |
| // Labels from resource a overwrite labels from resource b. |
| for k, v := range a.Labels { |
| res.Labels[k] = v |
| } |
| return res |
| } |
| |
| // Detector attempts to detect resource information. |
| // If the detector cannot find resource information, the returned resource is nil but no |
| // error is returned. |
| // An error is only returned on unexpected failures. |
| type Detector func(context.Context) (*Resource, error) |
| |
| // MultiDetector returns a Detector that calls all input detectors in order and |
| // merges each result with the previous one. In case a type of label key is already set, |
| // the first set value is takes precedence. |
| // It returns on the first error that a sub-detector encounters. |
| func MultiDetector(detectors ...Detector) Detector { |
| return func(ctx context.Context) (*Resource, error) { |
| return detectAll(ctx, detectors...) |
| } |
| } |
| |
| // detectall calls all input detectors sequentially an merges each result with the previous one. |
| // It returns on the first error that a sub-detector encounters. |
| func detectAll(ctx context.Context, detectors ...Detector) (*Resource, error) { |
| var res *Resource |
| for _, d := range detectors { |
| r, err := d(ctx) |
| if err != nil { |
| return nil, err |
| } |
| res = merge(res, r) |
| } |
| return res, nil |
| } |