pkg/bundle/builders.go (140 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 bundle
import (
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"google.golang.org/protobuf/encoding/protojson"
bundlev1 "github.com/elastic/harp/api/gen/go/harp/bundle/v1"
"github.com/elastic/harp/pkg/bundle/compare"
"github.com/elastic/harp/pkg/bundle/hcl"
"github.com/elastic/harp/pkg/bundle/secret"
"github.com/elastic/harp/pkg/sdk/types"
)
// FromDump creates a bundle from a JSON Dump.
func FromDump(r io.Reader) (*bundlev1.Bundle, error) {
// Check parameters
if types.IsNil(r) {
return nil, fmt.Errorf("unable to process nil reader")
}
// Drain input content
content, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("unable to read input content: %w", err)
}
// Build the container from json
var b bundlev1.Bundle
if err = protojson.Unmarshal(content, &b); err != nil {
return nil, fmt.Errorf("unable to decode JSON bundle: %w", err)
}
// Convert secret values to current value packing method.
for _, p := range b.Packages {
for _, s := range p.Secrets.Data {
// Decode json encoded value
var data interface{}
if errJSON := json.Unmarshal(s.Value, &data); errJSON != nil {
return nil, fmt.Errorf("unable to decode '%s' - '%s' secret value as json: %w", p.Name, s.Key, errJSON)
}
// Pack secret value
payload, err := secret.Pack(data)
if err != nil {
return nil, fmt.Errorf("unable to pack '%s' - '%s' secret value: %w", p.Name, s.Key, err)
}
// Replace current json encoded secret value by packed one.
s.Value = payload
}
}
// No error
return &b, nil
}
// FromOpLog convert oplog to a bundle.
func FromOpLog(oplog compare.OpLog) (*bundlev1.Bundle, error) {
// Create an empty bundle.
b := &bundlev1.Bundle{}
packageMap := map[string]*bundlev1.Package{}
// Generate patch rules
for _, op := range oplog {
switch op.Type {
case "package":
// Ignore package operation
continue
case "secret":
pathParts := strings.SplitN(op.Path, "#", 2)
pkg, ok := packageMap[pathParts[0]]
if !ok {
packageMap[pathParts[0]] = &bundlev1.Package{
Name: pathParts[0],
Secrets: &bundlev1.SecretChain{
Data: []*bundlev1.KV{},
},
}
pkg = packageMap[pathParts[0]]
}
// Process oplog event
switch op.Operation {
case compare.Add, compare.Replace:
// Pack secret value
payload, err := secret.Pack(op.Value)
if err != nil {
return nil, fmt.Errorf("unable to pack secret value for '%s' / '%s': %w", pathParts[0], pathParts[1], err)
}
// Assign secret data
pkg.Secrets.Data = append(pkg.Secrets.Data, &bundlev1.KV{
Key: pathParts[1],
Type: "string",
Value: payload,
})
case compare.Remove:
// Ignore secret removal
}
default:
return nil, fmt.Errorf("unknown oplog type '%s'", op.Type)
}
}
// Assign packages
for _, p := range packageMap {
b.Packages = append(b.Packages, p)
}
// No error
return b, nil
}
// FromMap builds a secret container from map K/V.
func FromMap(input map[string]KV) (*bundlev1.Bundle, error) {
// Check input
if input == nil {
return nil, fmt.Errorf("unable to process nil map")
}
res := &bundlev1.Bundle{
Packages: []*bundlev1.Package{},
}
for packageName, secretKv := range input {
// Prepare a package
p := &bundlev1.Package{
Name: packageName,
Secrets: &bundlev1.SecretChain{},
}
// Prepare secret data
for k, v := range secretKv {
// Pack secret value
packed, err := secret.Pack(v)
if err != nil {
return nil, fmt.Errorf("unable to pack secret value for `%s`: %w", fmt.Sprintf("%s.%s", packageName, k), err)
}
// Add to secret package
p.Secrets.Data = append(p.Secrets.Data, &bundlev1.KV{
Key: k,
Type: fmt.Sprintf("%T", v),
Value: packed,
})
}
// Add package to result
res.Packages = append(res.Packages, p)
}
// No error
return res, nil
}
// FromHCL convert HCL-DSL to a bundle.
func FromHCL(input *hcl.Config) (*bundlev1.Bundle, error) {
// Check arguments
if input == nil {
return nil, errors.New("unable to process nil hcl object")
}
// Create an empty bundle.
res := &bundlev1.Bundle{
Labels: input.Labels,
Annotations: input.Annotations,
Packages: []*bundlev1.Package{},
}
for _, pkg := range input.Packages {
// Prepare a package
p := &bundlev1.Package{
Name: pkg.Path,
Annotations: pkg.Annotations,
Labels: pkg.Labels,
Secrets: &bundlev1.SecretChain{},
}
// Prepare secret data
for k, v := range pkg.Secrets {
// Pack secret value
packed, err := secret.Pack(v)
if err != nil {
return nil, fmt.Errorf("unable to pack secret value for `%s`: %w", fmt.Sprintf("%s.%s", pkg.Path, k), err)
}
// Add to secret package
p.Secrets.Data = append(p.Secrets.Data, &bundlev1.KV{
Key: k,
Type: fmt.Sprintf("%T", v),
Value: packed,
})
}
// Add package to result
res.Packages = append(res.Packages, p)
}
// No error
return res, nil
}