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 2 commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import (
"path/filepath"
"testing"

"github.com/google/go-cmp/cmp"

"github.com/elastic/elastic-agent/internal/pkg/agent/application/secret"
"github.com/elastic/elastic-agent/internal/pkg/agent/vault"
"github.com/google/go-cmp/cmp"
)

const (
Expand Down
4 changes: 4 additions & 0 deletions internal/pkg/core/plugin/process/stdlogger.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package process

import (
Expand Down
4 changes: 4 additions & 0 deletions internal/pkg/core/plugin/process/stdlogger_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package process

import (
Expand Down
25 changes: 25 additions & 0 deletions pkg/component/load.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

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
}
70 changes: 70 additions & 0 deletions pkg/component/load_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package component

import (
"io/ioutil"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
)

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)
})
}
}
21 changes: 21 additions & 0 deletions pkg/component/outputs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

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}
71 changes: 71 additions & 0 deletions pkg/component/platforms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

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 (
// AMD64 represents the amd64 architecture
AMD64 = "amd64"
// ARM64 represents the arm64 architecture
ARM64 = "arm64"
)

// 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: Darwin,
Arch: AMD64,
GOOS: Darwin,
},
{
OS: Darwin,
Arch: ARM64,
GOOS: Darwin,
},
{
OS: Linux,
Arch: AMD64,
GOOS: Linux,
},
{
OS: Linux,
Arch: ARM64,
GOOS: Linux,
},
{
OS: Windows,
Arch: AMD64,
GOOS: Windows,
},
{
OS: Windows,
Arch: ARM64,
GOOS: Windows,
},
}
120 changes: 120 additions & 0 deletions pkg/component/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

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