Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
31 changes: 30 additions & 1 deletion cmd/create_package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,35 @@ func TestCreatePackageDescriptorFromAnswers_InputWithSubobjects(t *testing.T) {
assert.Nil(t, descriptor.Manifest.Elasticsearch)
}

func TestCreatePackageDescriptorFromAnswers_Blueprint(t *testing.T) {
answers := newPackageAnswers{
Type: "blueprint",
Name: "my_blueprint",
Version: "0.0.1",
SourceLicense: licenses.Elastic20,
Title: "my_blueprint",
Description: "This is a new package.",
Categories: []string{"custom"},
KibanaVersion: tui.DefaultKibanaVersionConditionValue(),
ElasticSubscription: "basic",
GithubOwner: "elastic/integrations",
OwnerType: "elastic",
DataStreamType: "logs",
Subobjects: false,
}

descriptor := createPackageDescriptorFromAnswers(answers)

assert.Equal(t, "blueprint", descriptor.Manifest.Type)
assert.Equal(t, "my_blueprint", descriptor.Manifest.Name)
assert.Equal(t, "0.0.1", descriptor.Manifest.Version)
assert.Equal(t, licenses.Elastic20, descriptor.Manifest.Source.License)
// Blueprint packages have no data streams and no Elasticsearch overrides,
// even if the wizard collected default data-stream answers.
assert.Empty(t, descriptor.InputDataStreamType)
assert.Nil(t, descriptor.Manifest.Elasticsearch)
}

func TestCreatePackageDescriptorFromAnswers_NoLicense(t *testing.T) {
answers := newPackageAnswers{
Type: "integration",
Expand All @@ -116,7 +145,7 @@ func TestCreatePackageDescriptorFromAnswers_NoLicense(t *testing.T) {
}

func TestAllowedPackageTypes(t *testing.T) {
valid := []string{"input", "integration", "content"}
valid := []string{"input", "integration", "content", "blueprint"}
for _, v := range valid {
assert.Contains(t, packages.AllowedPackageTypes, v, "expected %q to be in AllowedPackageTypes", v)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cobraext/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const (
CreatePackageNameFlagDescription = "set to create an empty package with the given name in a non-interactive way"

CreatePackageTypeFlagName = "type"
CreatePackageTypeFlagDescription = "set to 'input', 'integration' or 'content' to create an empty package of the given type in a non-interactive way"
CreatePackageTypeFlagDescription = "set to 'input', 'integration', 'content' or 'blueprint' to create an empty package of the given type in a non-interactive way"

CreateDataStreamNameFlagName = "name"
CreateDataStreamNameFlagDescription = "set to create a data stream with the given name in a non-interactive way"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Sample canonical base blueprint. Replace with the real IaC template for this provider/trust-model/scope.",
"Parameters": {
"ElasticResourceId": {
"Type": "String",
"Description": "Elastic resource ID used to construct trust conditions."
}
},
"Resources": {
"ElasticFederatedIdentityRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": { "Fn::Sub": "ElasticFederatedIdentity-${AWS::StackName}" },
"Path": "/",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": []
},
"ManagedPolicyArns": []
}
}
},
"Outputs": {
"RoleArn": {
"Description": "ARN of the Elastic Federated Identity role.",
"Value": { "Fn::GetAtt": ["ElasticFederatedIdentityRole", "Arn"] }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ source:
{[ end ]}
description: "{[.Manifest.Description]}"
type: {[.Manifest.Type]}
{[- if ne .Manifest.Type "blueprint" ]}
categories:{[range $category := .Manifest.Categories]}
- {[$category]}
{[- end]}
{[- end ]}
conditions:
kibana:
version: "{[.Manifest.Conditions.Kibana.Version]}"
Expand All @@ -20,6 +22,7 @@ conditions:
discovery:
fields: []
{[- end ]}
{[- if ne .Manifest.Type "blueprint" ]}
screenshots:
- src: /img/sample-screenshot.png
title: Sample screenshot
Expand All @@ -30,6 +33,7 @@ icons:
title: Sample logo
size: 32x32
type: image/svg+xml
{[- end ]}
{[- if (or (eq .Manifest.Type "integration") (eq .Manifest.Type "input")) ]}
policy_templates:
{[- if eq .Manifest.Type "integration" ]}
Expand Down
48 changes: 31 additions & 17 deletions internal/packages/archetype/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,13 @@ func createPackageInDir(packageDescriptor PackageDescriptor, cwd string) error {
return fmt.Errorf("can't render package README: %w", err)
}

logger.Debugf("Write docs readme template to _dev")
err = renderResourceFile(packageDocsReadme, &packageDescriptor, filepath.Join(baseDir, "_dev", "build", "docs", "README.md"))
if err != nil {
return fmt.Errorf("can't render package README in _dev: %w", err)
// Blueprint packages do not allow a `_dev` directory in package-spec.
if packageDescriptor.Manifest.Type != "blueprint" {
logger.Debugf("Write docs readme template to _dev")
err = renderResourceFile(packageDocsReadme, &packageDescriptor, filepath.Join(baseDir, "_dev", "build", "docs", "README.md"))
if err != nil {
return fmt.Errorf("can't render package README in _dev: %w", err)
}
}

if license := packageDescriptor.Manifest.Source.License; license != "" {
Expand All @@ -68,20 +71,23 @@ func createPackageInDir(packageDescriptor PackageDescriptor, cwd string) error {
}
}

logger.Debugf("Write sample icon")
err = writeRawResourceFile(packageImgSampleIcon, filepath.Join(baseDir, "img", "sample-logo.svg"))
if err != nil {
return fmt.Errorf("can't render sample icon: %w", err)
}
// Blueprint packages do not ship Kibana assets or package icons.
if packageDescriptor.Manifest.Type != "blueprint" {
logger.Debugf("Write sample icon")
err = writeRawResourceFile(packageImgSampleIcon, filepath.Join(baseDir, "img", "sample-logo.svg"))
if err != nil {
return fmt.Errorf("can't render sample icon: %w", err)
}

logger.Debugf("Write sample screenshot")
decodedSampleScreenshot, err := decodeBase64Resource(packageImgSampleScreenshot)
if err != nil {
return fmt.Errorf("can't decode sample screenshot: %w", err)
}
err = writeRawResourceFile(decodedSampleScreenshot, filepath.Join(baseDir, "img", "sample-screenshot.png"))
if err != nil {
return fmt.Errorf("can't render sample screenshot: %w", err)
logger.Debugf("Write sample screenshot")
decodedSampleScreenshot, err := decodeBase64Resource(packageImgSampleScreenshot)
if err != nil {
return fmt.Errorf("can't decode sample screenshot: %w", err)
}
err = writeRawResourceFile(decodedSampleScreenshot, filepath.Join(baseDir, "img", "sample-screenshot.png"))
if err != nil {
return fmt.Errorf("can't render sample screenshot: %w", err)
}
}

if packageDescriptor.Manifest.Type == "integration" {
Expand All @@ -107,6 +113,14 @@ func createPackageInDir(packageDescriptor PackageDescriptor, cwd string) error {
}
}

if packageDescriptor.Manifest.Type == "blueprint" {
logger.Debugf("Write sample blueprint")
err = writeRawResourceFile(blueprintSampleCloudFormation, filepath.Join(baseDir, "blueprints", "aws", "federated-identity", "account.cloudformation.json"))
if err != nil {
return fmt.Errorf("can't render sample blueprint: %w", err)
}
}

if len(packageDescriptor.ExcludeChecks) > 0 {
logger.Debugf("Write validation file")
err = renderResourceFile(validationBaseTemplate, &packageDescriptor, filepath.Join(baseDir, "validation.yml"))
Expand Down
42 changes: 42 additions & 0 deletions internal/packages/archetype/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,48 @@ func TestPackage(t *testing.T) {
pd := createPackageDescriptorForTest("content", "^8.16.0")
createAndCheckPackage(t, pd, true)
})
t.Run("blueprint-package", func(t *testing.T) {
pd := createPackageDescriptorForTest("blueprint", "^8.0.0")
createAndCheckBlueprintPackage(t, pd)
})
}

// createAndCheckBlueprintPackage verifies blueprint scaffolding and package-root
// detection. Full package-spec validation is skipped until package-spec publishes
// the blueprint type (elastic/package-spec#1209).
func createAndCheckBlueprintPackage(t *testing.T, pd PackageDescriptor) {
t.Helper()

repositoryRoot, err := os.OpenRoot(t.TempDir())
require.NoError(t, err)
defer repositoryRoot.Close()

packagesDir := filepath.Join(repositoryRoot.Name(), "packages")
err = createPackageInDir(pd, packagesDir)
require.NoError(t, err)

packageRoot := filepath.Join(packagesDir, pd.Manifest.Name)
root, err := packages.FindPackageRootFrom(packageRoot)
require.NoError(t, err)
assert.Equal(t, packageRoot, root)

manifest, err := packages.ReadPackageManifestFromPackageRoot(packageRoot)
require.NoError(t, err)
assert.Equal(t, "blueprint", manifest.Type)
assert.Empty(t, manifest.Categories)

manifestBytes, err := os.ReadFile(filepath.Join(packageRoot, packages.PackageManifestFile))
require.NoError(t, err)
assert.NotContains(t, string(manifestBytes), "icons:")
assert.NotContains(t, string(manifestBytes), "screenshots:")
assert.NotContains(t, string(manifestBytes), "categories:")

_, err = os.Stat(filepath.Join(packageRoot, "blueprints", "aws", "federated-identity", "account.cloudformation.json"))
require.NoError(t, err)
_, err = os.Stat(filepath.Join(packageRoot, "img"))
require.ErrorIs(t, err, os.ErrNotExist)
_, err = os.Stat(filepath.Join(packageRoot, "_dev"))
require.ErrorIs(t, err, os.ErrNotExist)
}

func createAndCheckPackage(t *testing.T, pd PackageDescriptor, valid bool) {
Expand Down
3 changes: 3 additions & 0 deletions internal/packages/archetype/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ var fieldsBaseTemplate string
//go:embed _static/package-validation.yml.tmpl
var validationBaseTemplate string

//go:embed _static/blueprint-sample.cloudformation.json
var blueprintSampleCloudFormation []byte

// Images (logo and screenshot)

//go:embed _static/sampleIcon.svg
Expand Down
5 changes: 3 additions & 2 deletions internal/packages/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ const (
dataStreamTypeTraces = "traces"
)

// AllowedPackageTypes lists the valid package types accepted by the create wizard.
var AllowedPackageTypes = []string{"input", "integration", "content"}
// AllowedPackageTypes lists the valid package types accepted by package root
// detection and the create wizard.
var AllowedPackageTypes = []string{"input", "integration", "content", "blueprint"}

// AllowedDataStreamTypes lists the data stream types accepted by the create wizard.
var AllowedDataStreamTypes = []string{"logs", "metrics"}
Expand Down
59 changes: 59 additions & 0 deletions internal/packages/packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package packages

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -383,3 +384,61 @@ latest:
})
}
}

func TestFindPackageRootFrom_AllowedPackageTypes(t *testing.T) {
for _, pkgType := range AllowedPackageTypes {
t.Run(pkgType, func(t *testing.T) {
packageDir := t.TempDir()
manifest := fmt.Sprintf(`format_version: 3.6.6
name: test_%s_pkg
title: Test %s Package
description: Test package for FindPackageRootFrom.
version: 0.0.1
type: %s
owner:
github: elastic/integrations
type: elastic
`, pkgType, pkgType, pkgType)
err := os.WriteFile(filepath.Join(packageDir, PackageManifestFile), []byte(manifest), 0o644)
require.NoError(t, err)

root, err := FindPackageRootFrom(packageDir)
require.NoError(t, err)
assert.Equal(t, packageDir, root)
})
}

t.Run("unknown type is not a package root", func(t *testing.T) {
packageDir := t.TempDir()
manifest := `format_version: 3.6.6
name: test_unknown_pkg
title: Test Unknown Package
description: Should not be recognized as a package root.
version: 0.0.1
type: unknown
owner:
github: elastic/integrations
type: elastic
`
err := os.WriteFile(filepath.Join(packageDir, PackageManifestFile), []byte(manifest), 0o644)
require.NoError(t, err)

_, err = FindPackageRootFrom(packageDir)
require.ErrorIs(t, err, ErrPackageRootNotFound)
})
}

func TestAllowedPackageTypesIncludesBlueprint(t *testing.T) {
assert.Contains(t, AllowedPackageTypes, "blueprint")
}

func TestFindPackageRootFrom_BlueprintFixture(t *testing.T) {
packageRoot := filepath.Join("testdata", "blueprint")
root, err := FindPackageRootFrom(packageRoot)
require.NoError(t, err)
assert.Equal(t, packageRoot, root)

manifest, err := ReadPackageManifestFromPackageRoot(root)
require.NoError(t, err)
assert.Equal(t, "blueprint", manifest.Type)
}
Loading