Skip to content

Commit

Permalink
Add resourcedetection for cycle.io platform (#100)
Browse files Browse the repository at this point in the history
Co-authored-by: Tejas Kokje <[email protected]>
  • Loading branch information
bhogayatakb and tejaskokje-mw authored Dec 19, 2024
1 parent 0207370 commit 748c1a9
Show file tree
Hide file tree
Showing 6 changed files with 344 additions and 0 deletions.
6 changes: 6 additions & 0 deletions processor/resourcedetectionprocessor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/azure"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/azure/aks"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/consul"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/cycleio"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/docker"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/gcp"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/heroku"
Expand Down Expand Up @@ -85,6 +86,9 @@ type DetectorConfig struct {

// K8SNode contains user-specified configurations for the K8SNode detector
K8SNodeConfig k8snode.Config `mapstructure:"k8snode"`

// CycleIO contains user-specified configurations for the CycleIO detector
CycleIOConfig cycleio.Config `mapstructure:"cycleio"`
}

func detectorCreateDefaultConfig() DetectorConfig {
Expand Down Expand Up @@ -136,6 +140,8 @@ func (d *DetectorConfig) GetConfigFromType(detectorType internal.DetectorType) i
return d.OpenShiftConfig
case k8snode.TypeStr:
return d.K8SNodeConfig
case cycleio.TypeStr:
return d.CycleIOConfig
default:
return nil
}
Expand Down
2 changes: 2 additions & 0 deletions processor/resourcedetectionprocessor/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/azure"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/azure/aks"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/consul"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/cycleio"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/docker"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/env"
"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/gcp"
Expand Down Expand Up @@ -66,6 +67,7 @@ func NewFactory() processor.Factory {
system.TypeStr: system.NewDetector,
openshift.TypeStr: openshift.NewDetector,
k8snode.TypeStr: k8snode.NewDetector,
cycleio.TypeStr: cycleio.NewDetector,
})

f := &factory{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package cycleio

type Config struct {
}

func CreateDefaultConfig() Config {
return Config{}
}
184 changes: 184 additions & 0 deletions processor/resourcedetectionprocessor/internal/cycleio/cycleio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// Package env provides a detector that loads resource information from
// the OTEL_RESOURCE environment variable. A list of labels of the form
// `<key1>=<value1>,<key2>=<value2>,...` is accepted. Domain names and
// paths are accepted as label keys.
package cycleio // import "github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal/cycleio"

import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"

"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/processor"
semconv "go.opentelemetry.io/collector/semconv/v1.5.0"

"github.com/open-telemetry/opentelemetry-collector-contrib/processor/resourcedetectionprocessor/internal"
)

// TypeStr is type of detector.
const (
TypeStr = "cycleio"
providerVendorEnvVar = "CYCLE_PROVIDER_VENDOR"
providerLocation = "CYCLE_PROVIDER_LOCATION"
hostnameEnvVar = "CYCLE_SERVER_ID"
clusterEnvVar = "CYCLE_CLUSTER"

cycleAPITokenEnvVar = "CYCLE_API_TOKEN"
cycleAPIUnixSocket = "/var/run/cycle/api/api.sock"
cycleAPIHost = "localhost"
cycleAPIServerEndpoint = "/v1/server"

cycleTokenHeader = "X-CYCLE-TOKEN"
)

var _ internal.Detector = (*Detector)(nil)

type cycleProvider struct {
Vendor string `json:"vendor"`
Model string `json:"model"`
Location string `json:"location"`
Zone string `json:"zone"`
Server string `json:"server"`
InitIPs []any `json:"init_ips"`
}

type cycleServerData struct {
ID string `json:"id"`
Hostname string `json:"hostname"`
Provider cycleProvider `json:"provider"`
Cluster string `json:"cluster"`
}

type cycleServerInfo struct {
Data cycleServerData `json:"data"`
}

type Detector struct{}

func NewDetector(processor.Settings, internal.DetectorConfig) (internal.Detector, error) {
return &Detector{}, nil
}

func (d *Detector) Detect(context.Context) (resource pcommon.Resource, schemaURL string, err error) {
res := pcommon.NewResource()

res.Attributes().PutStr(semconv.AttributeOSType, semconv.AttributeOSTypeLinux)
cycleAPIToken := os.Getenv(cycleAPITokenEnvVar)
if cycleAPIToken != "" {
serverInfo, err := getServerInfo(cycleAPIToken)
if err != nil {
return res, "", err
}
data := serverInfo.Data

res.Attributes().PutStr(semconv.AttributeCloudProvider, getCloudProvider(data.Provider.Vendor))
res.Attributes().PutStr(semconv.AttributeCloudRegion, data.Provider.Location)
res.Attributes().PutStr(semconv.AttributeCloudAvailabilityZone, data.Provider.Zone)
res.Attributes().PutStr(semconv.AttributeHostID, data.ID)
res.Attributes().PutStr(semconv.AttributeHostName, data.Hostname)
res.Attributes().PutStr(semconv.AttributeHostType, data.Provider.Model)
res.Attributes().PutEmptySlice("host.ip").FromRaw(data.Provider.InitIPs)

res.Attributes().PutStr("cycle.cluster.id", data.Cluster)
} else {

vendor := os.Getenv(providerVendorEnvVar)
if vendor == "" {
vendor = "unknown"
}
res.Attributes().PutStr(semconv.AttributeCloudProvider, getCloudProvider(vendor))

region := os.Getenv(providerLocation)
if region == "" {
region = "unknown"
}
res.Attributes().PutStr(semconv.AttributeCloudRegion, region)

hostID := os.Getenv(hostnameEnvVar)
if hostID == "" {
hostID = "cycleio-server"
}
res.Attributes().PutStr(semconv.AttributeHostID, hostID)
res.Attributes().PutStr(semconv.AttributeHostName, hostID)

cluster := os.Getenv(clusterEnvVar)
if cluster == "" {
cluster = "unknown"
}
res.Attributes().PutStr("cycle.cluster.id", cluster)
}

return res, "", nil
}

func getCloudProvider(provider string) string {
switch provider {
case "aws":
return semconv.AttributeCloudProviderAWS
case "gcp":
return semconv.AttributeCloudProviderGCP
case "azure":
return semconv.AttributeCloudProviderAzure
default:
return provider
}
}

func getServerInfo(token string) (*cycleServerInfo, error) {
var serverInfo cycleServerInfo

// Create a custom HTTP transport that uses the Unix socket
transport := &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", cycleAPIUnixSocket)
},
}

// Create an HTTP client with the custom transport
client := &http.Client{
Transport: transport,
}

// Construct the request URL
u := &url.URL{
Scheme: "http",
Host: cycleAPIHost, // This is ignored but required for forming a valid URL
Path: cycleAPIServerEndpoint,
}

req, err := http.NewRequest("GET", u.String(), nil)
if err != nil {
return &serverInfo, err
}
req.Header.Add(cycleTokenHeader, token)
resp, err := client.Do(req)
if err != nil {
return &serverInfo, err
}

defer resp.Body.Close()

// Read and print the response
body, err := io.ReadAll(resp.Body)
if err != nil {
return &serverInfo, err
}

fmt.Println("###", string(body))
err = json.Unmarshal(body, &serverInfo)
if err != nil {
return &serverInfo, err
}

fmt.Println("###", serverInfo)
return &serverInfo, nil
}
130 changes: 130 additions & 0 deletions processor/resourcedetectionprocessor/internal/cycleio/cycleio_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package cycleio

import (
"context"
"os"
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/processor"
semconv "go.opentelemetry.io/collector/semconv/v1.5.0"
)

// TODO
func TestDetect(t *testing.T) {
tests := []struct {
name string
env map[string]string
want map[string]interface{}
}{
{
name: "empty env",
env: map[string]string{},
want: map[string]interface{}{
semconv.AttributeCloudProvider: "unknown",
semconv.AttributeCloudRegion: "unknown",
semconv.AttributeHostID: "cycleio-server",
semconv.AttributeHostName: "cycleio-server",
semconv.AttributeOSType: semconv.AttributeOSTypeLinux,
"cycle.cluster.id": "unknown",
},
},
{
name: "only provider vendor",
env: map[string]string{
providerVendorEnvVar: "aws",
},
want: map[string]interface{}{
semconv.AttributeCloudProvider: "aws",
semconv.AttributeCloudRegion: "unknown",
semconv.AttributeHostID: "cycleio-server",
semconv.AttributeHostName: "cycleio-server",
semconv.AttributeOSType: semconv.AttributeOSTypeLinux,
"cycle.cluster.id": "unknown",
},
},
{
name: "only provider location",
env: map[string]string{
providerVendorEnvVar: "aws",
providerLocation: "us-east-1",
},
want: map[string]interface{}{
semconv.AttributeCloudProvider: "aws",
semconv.AttributeCloudRegion: "us-east-1",
semconv.AttributeHostID: "cycleio-server",
semconv.AttributeHostName: "cycleio-server",
semconv.AttributeOSType: semconv.AttributeOSTypeLinux,
"cycle.cluster.id": "unknown",
},
},
{
name: "only hostname",
env: map[string]string{
hostnameEnvVar: "acme-host",
},
want: map[string]interface{}{
semconv.AttributeCloudProvider: "unknown",
semconv.AttributeCloudRegion: "unknown",
semconv.AttributeHostID: "acme-host",
semconv.AttributeHostName: "acme-host",
semconv.AttributeOSType: semconv.AttributeOSTypeLinux,
"cycle.cluster.id": "unknown",
},
},
{
name: "only cluster",
env: map[string]string{
clusterEnvVar: "acme-cluster",
},
want: map[string]interface{}{
semconv.AttributeCloudProvider: "unknown",
semconv.AttributeCloudRegion: "unknown",
semconv.AttributeHostID: "cycleio-server",
semconv.AttributeHostName: "cycleio-server",
semconv.AttributeOSType: semconv.AttributeOSTypeLinux,
"cycle.cluster.id": "acme-cluster",
},
},
{
name: "all env vars",
env: map[string]string{
providerVendorEnvVar: "aws",
providerLocation: "us-east-1",
hostnameEnvVar: "acme-host",
clusterEnvVar: "acme-cluster",
},
want: map[string]interface{}{
semconv.AttributeCloudProvider: "aws",
semconv.AttributeCloudRegion: "us-east-1",
semconv.AttributeHostID: "acme-host",
semconv.AttributeHostName: "acme-host",
semconv.AttributeOSType: semconv.AttributeOSTypeLinux,
"cycle.cluster.id": "acme-cluster",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for key, value := range tt.env {
os.Setenv(key, value)
}
defer func() {
for key := range tt.env {
os.Unsetenv(key)
}
}()

d, err := NewDetector(processor.Settings{}, CreateDefaultConfig())
require.NoError(t, err)
got, _, err := d.Detect(context.Background())
require.NoError(t, err)

require.Equal(t, tt.want, got.Attributes().AsRaw())
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package cycleio

import (
"testing"

"go.uber.org/goleak"
)

func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}

0 comments on commit 748c1a9

Please sign in to comment.