Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v2] Add v2 component specification and validation. #502

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions pkg/component/load.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package component

import (
"github.com/elastic/go-ucfg/yaml"
)

// LoadSpec loads the component specification.
//
// Will error in the case that the specification is not valid. Only valid specifications are allowed.
func LoadSpec(data []byte) (Spec, error) {
var spec Spec
cfg, err := yaml.NewConfig(data)
if err != nil {
return spec, err
}
err = cfg.Unpack(&spec)
if err != nil {
return spec, err
}
return spec, nil
}
65 changes: 65 additions & 0 deletions pkg/component/load_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package component

import (
"github.com/stretchr/testify/require"
"io/ioutil"
"path/filepath"
"testing"
)

func TestLoadSpec_Components(t *testing.T) {
scenarios := []struct {
Name string
Path string
}{
{
Name: "APM Server",
Path: "apm-server.yml",
},
{
Name: "Auditbeat",
Path: "auditbeat.yml",
},
{
Name: "Cloudbeat",
Path: "cloudbeat.yml",
},
{
Name: "Endpoint Security",
Path: "endpoint-security.yml",
},
{
Name: "Filebeat",
Path: "filebeat.yml",
},
{
Name: "Fleet Server",
Path: "fleet-server.yml",
},
{
Name: "Heartbeat",
Path: "heartbeat.yml",
},
{
Name: "Metricbeat",
Path: "metricbeat.yml",
},
{
Name: "Osquerybeat",
Path: "osquerybeat.yml",
},
{
Name: "Packetbeat",
Path: "packetbeat.yml",
},
}

for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
data, err := ioutil.ReadFile(filepath.Join("..", "..", "specs", scenario.Path))
require.NoError(t, err)
_, err = LoadSpec(data)
require.NoError(t, err)
})
}
}
17 changes: 17 additions & 0 deletions pkg/component/outputs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package component

const (
// Elasticsearch represents the elasticsearch output
Elasticsearch = "elasticsearch"
// Kafka represents the kafka output
Kafka = "kafka"
// Logstash represents the logstash output
Logstash = "logstash"
// Redis represents the redis output
Redis = "redis"
// Shipper represents support for using the elastic-agent-shipper
Shipper = "shipper"
)

// Outputs defines the outputs that a component can support
var Outputs = []string{Elasticsearch, Kafka, Logstash, Redis, Shipper}
91 changes: 91 additions & 0 deletions pkg/component/platforms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package component

const (
// Container represents running inside a container
Container = "container"
// Darwin represents running on Mac OSX
Darwin = "darwin"
// Linux represents running on Linux
Linux = "linux"
// Windows represents running on Windows
Windows = "windows"
)

const (
// I386 represents the i386 architecture
I386 = "386"
blakerouse marked this conversation as resolved.
Show resolved Hide resolved
// AMD64 represents the amd64 architecture
AMD64 = "amd64"
// ARM64 represents the arm64 architecture
ARM64 = "arm64"
// PPC64 represents the ppc64el architecture
PPC64 = "ppc64el"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove this, we do not support ppc64.
It seems we are missing the aarch64? and later universal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between arm64 and aarch64?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind I was in the potato field.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove ppc64el, I don't see it as a supported platform.

)

// Platforms defines the platforms that a component can support
var Platforms = []struct {
OS string
Arch string
GOOS string
}{
{
OS: Container,
Arch: AMD64,
GOOS: Linux,
},
{
OS: Container,
Arch: ARM64,
GOOS: Linux,
},
{
OS: Container,
Arch: PPC64,
GOOS: Linux,
},
{
OS: Darwin,
Arch: AMD64,
GOOS: Darwin,
},
{
OS: Darwin,
Arch: ARM64,
GOOS: Darwin,
},
{
OS: Linux,
Arch: I386,
GOOS: Linux,
},
{
OS: Linux,
Arch: AMD64,
GOOS: Linux,
},
{
OS: Linux,
Arch: ARM64,
GOOS: Linux,
},
{
OS: Linux,
Arch: PPC64,
GOOS: Linux,
},
{
OS: Windows,
Arch: I386,
GOOS: Windows,
},
{
OS: Windows,
Arch: AMD64,
GOOS: Windows,
},
{
OS: Windows,
Arch: ARM64,
GOOS: Windows,
},
}
116 changes: 116 additions & 0 deletions pkg/component/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package component

import (
"errors"
"fmt"
"time"
)

// Spec a components specification.
type Spec struct {
Version int `config:"version" yaml:"version" validate:"required"`
Inputs []InputSpec `config:"inputs,omitempty" yaml:"inputs,omitempty"`
}

// Validate ensures correctness of component specification.
func (s *Spec) Validate() error {
if s.Version != 2 {
return errors.New("only version 2 is allowed")
}
inputsToPlatforms := make(map[string][]string)
for i, input := range s.Inputs {
a, ok := inputsToPlatforms[input.Name]
if !ok {
inputsToPlatforms[input.Name] = make([]string, len(input.Platforms))
copy(inputsToPlatforms[input.Name], input.Platforms)
continue
}
for _, platform := range input.Platforms {
for _, existing := range a {
if existing == platform {
return fmt.Errorf("input %s at inputs.%d defines the same platform as a previous definition", input.Name, i)
}
}
a = append(a, platform)
inputsToPlatforms[input.Name] = a
}
}
return nil
}

// InputSpec is the specification for an input type.
type InputSpec struct {
Name string `config:"name" yaml:"name" validate:"required"`
Aliases []string `config:"aliases,omitempty" yaml:"aliases,omitempty"`
Description string `config:"description" yaml:"description" validate:"required"`
Platforms []string `config:"platforms" yaml:"platforms" validate:"required,min=1"`
Outputs []string `config:"outputs" yaml:"outputs" validate:"required,min=1"`
Runtime RuntimeSpec `config:"runtime" yaml:"runtime"`

Command *CommandSpec `config:"command,omitempty" yaml:"command,omitempty"`
Service *ServiceSpec `config:"service,omitempty" yaml:"service,omitempty"`
}

// Validate ensures correctness of input specification.
func (s *InputSpec) Validate() error {
if s.Command == nil && s.Service == nil {
return fmt.Errorf("input %s must define either command or service", s.Name)
}
for i, a := range s.Platforms {
for j, b := range s.Platforms {
if i != j && a == b {
return fmt.Errorf("input %s defines the platform %s more than once", s.Name, a)
}
}
}
for i, a := range s.Outputs {
for j, b := range s.Outputs {
if i != j && a == b {
return fmt.Errorf("input %s defines the output %s more than once", s.Name, a)
}
}
}
return nil
}

// RuntimeSpec is the specification for runtime options.
type RuntimeSpec struct {
Preventions []RuntimePreventionSpec `config:"preventions" yaml:"preventions"`
}

// RuntimePreventionSpec is the specification that prevents an input to run at execution time.
type RuntimePreventionSpec struct {
Condition string `config:"condition" yaml:"condition" validate:"required"`
Message string `config:"message" yaml:"message" validate:"required"`
}

// CommandSpec is the specification for an input that executes as a subprocess.
type CommandSpec struct {
Args []string `config:"args,omitempty" yaml:"args,omitempty"`
Env []CommandEnvSpec `config:"env,omitempty" yaml:"env,omitempty"`
}

// CommandEnvSpec is the specification that defines environment variables that will be set to execute the subprocess.
type CommandEnvSpec struct {
Name string `config:"name" yaml:"name" validate:"required"`
Value string `config:"value" yaml:"value" validate:"required"`
}

// ServiceSpec is the specification for an input that executes as a service.
type ServiceSpec struct {
Operations ServiceOperationsSpec `config:"operations" yaml:"operations" validate:"required"`
}

// ServiceOperationsSpec is the specification of the operations that need to be performed to get a service installed/uninstalled.
type ServiceOperationsSpec struct {
Check *ServiceOperationsCommandSpec `config:"check,omitempty" yaml:"check,omitempty"`
Install *ServiceOperationsCommandSpec `config:"install" yaml:"install" validate:"required"`
Uninstall *ServiceOperationsCommandSpec `config:"uninstall" yaml:"uninstall" validate:"required"`
}

// ServiceOperationsCommandSpec is the specification for execution of binaries to perform the check, install, and uninstall.
type ServiceOperationsCommandSpec struct {
Args []string `config:"args,omitempty" yaml:"args,omitempty"`
Env []CommandEnvSpec `config:"env,omitempty" yaml:"env,omitempty"`
Timeout time.Duration `config:"timeout,omitempty" yaml:"timeout,omitempty"`
}
Loading