github.com/zntrio/harp/v2@v2.0.9/pkg/bundle/builders.go (about)

     1  // Licensed to Elasticsearch B.V. under one or more contributor
     2  // license agreements. See the NOTICE file distributed with
     3  // this work for additional information regarding copyright
     4  // ownership. Elasticsearch B.V. licenses this file to you under
     5  // the Apache License, Version 2.0 (the "License"); you may
     6  // not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package bundle
    19  
    20  import (
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"strings"
    26  
    27  	"google.golang.org/protobuf/encoding/protojson"
    28  
    29  	bundlev1 "github.com/zntrio/harp/v2/api/gen/go/harp/bundle/v1"
    30  	"github.com/zntrio/harp/v2/pkg/bundle/compare"
    31  	"github.com/zntrio/harp/v2/pkg/bundle/hcl"
    32  	"github.com/zntrio/harp/v2/pkg/bundle/secret"
    33  	"github.com/zntrio/harp/v2/pkg/sdk/types"
    34  )
    35  
    36  // FromDump creates a bundle from a JSON Dump.
    37  func FromDump(r io.Reader) (*bundlev1.Bundle, error) {
    38  	// Check parameters
    39  	if types.IsNil(r) {
    40  		return nil, fmt.Errorf("unable to process nil reader")
    41  	}
    42  
    43  	// Drain input content
    44  	content, err := io.ReadAll(r)
    45  	if err != nil {
    46  		return nil, fmt.Errorf("unable to read input content: %w", err)
    47  	}
    48  
    49  	// Build the container from json
    50  	var b bundlev1.Bundle
    51  	if err = protojson.Unmarshal(content, &b); err != nil {
    52  		return nil, fmt.Errorf("unable to decode JSON bundle: %w", err)
    53  	}
    54  
    55  	// Convert secret values to current value packing method.
    56  	for _, p := range b.Packages {
    57  		for _, s := range p.Secrets.Data {
    58  			// Decode json encoded value
    59  			var data interface{}
    60  			if errJSON := json.Unmarshal(s.Value, &data); errJSON != nil {
    61  				return nil, fmt.Errorf("unable to decode %q - %q secret value as json: %w", p.Name, s.Key, errJSON)
    62  			}
    63  
    64  			// Pack secret value
    65  			payload, err := secret.Pack(data)
    66  			if err != nil {
    67  				return nil, fmt.Errorf("unable to pack %q - %q secret value: %w", p.Name, s.Key, err)
    68  			}
    69  
    70  			// Replace current json encoded secret value by packed one.
    71  			s.Value = payload
    72  		}
    73  	}
    74  
    75  	// No error
    76  	return &b, nil
    77  }
    78  
    79  // FromOpLog convert oplog to a bundle.
    80  func FromOpLog(oplog compare.OpLog) (*bundlev1.Bundle, error) {
    81  	// Create an empty bundle.
    82  	b := &bundlev1.Bundle{}
    83  
    84  	packageMap := map[string]*bundlev1.Package{}
    85  
    86  	// Generate patch rules
    87  	for _, op := range oplog {
    88  		switch op.Type {
    89  		case "package":
    90  			// Ignore package operation
    91  			continue
    92  		case "secret":
    93  			pathParts := strings.SplitN(op.Path, "#", 2)
    94  			pkg, ok := packageMap[pathParts[0]]
    95  			if !ok {
    96  				packageMap[pathParts[0]] = &bundlev1.Package{
    97  					Name: pathParts[0],
    98  					Secrets: &bundlev1.SecretChain{
    99  						Data: []*bundlev1.KV{},
   100  					},
   101  				}
   102  				pkg = packageMap[pathParts[0]]
   103  			}
   104  
   105  			// Process oplog event
   106  			switch op.Operation {
   107  			case compare.Add, compare.Replace:
   108  				// Pack secret value
   109  				payload, err := secret.Pack(op.Value)
   110  				if err != nil {
   111  					return nil, fmt.Errorf("unable to pack secret value for %q / %q: %w", pathParts[0], pathParts[1], err)
   112  				}
   113  
   114  				// Assign secret data
   115  				pkg.Secrets.Data = append(pkg.Secrets.Data, &bundlev1.KV{
   116  					Key:   pathParts[1],
   117  					Type:  "string",
   118  					Value: payload,
   119  				})
   120  			case compare.Remove:
   121  				// Ignore secret removal
   122  			}
   123  		default:
   124  			return nil, fmt.Errorf("unknown oplog type %q", op.Type)
   125  		}
   126  	}
   127  
   128  	// Assign packages
   129  	for _, p := range packageMap {
   130  		b.Packages = append(b.Packages, p)
   131  	}
   132  
   133  	// No error
   134  	return b, nil
   135  }
   136  
   137  // FromMap builds a secret container from map K/V.
   138  func FromMap(input map[string]KV) (*bundlev1.Bundle, error) {
   139  	// Check input
   140  	if input == nil {
   141  		return nil, fmt.Errorf("unable to process nil map")
   142  	}
   143  
   144  	res := &bundlev1.Bundle{
   145  		Packages: []*bundlev1.Package{},
   146  	}
   147  	for packageName, secretKv := range input {
   148  		// Prepare a package
   149  		p := &bundlev1.Package{
   150  			Name:    packageName,
   151  			Secrets: &bundlev1.SecretChain{},
   152  		}
   153  
   154  		// Prepare secret data
   155  		for k, v := range secretKv {
   156  			// Pack secret value
   157  			packed, err := secret.Pack(v)
   158  			if err != nil {
   159  				return nil, fmt.Errorf("unable to pack secret value for `%s`: %w", fmt.Sprintf("%s.%s", packageName, k), err)
   160  			}
   161  
   162  			// Add to secret package
   163  			p.Secrets.Data = append(p.Secrets.Data, &bundlev1.KV{
   164  				Key:   k,
   165  				Type:  fmt.Sprintf("%T", v),
   166  				Value: packed,
   167  			})
   168  		}
   169  
   170  		// Add package to result
   171  		res.Packages = append(res.Packages, p)
   172  	}
   173  
   174  	// No error
   175  	return res, nil
   176  }
   177  
   178  // FromHCL convert HCL-DSL to a bundle.
   179  func FromHCL(input *hcl.Config) (*bundlev1.Bundle, error) {
   180  	// Check arguments
   181  	if input == nil {
   182  		return nil, errors.New("unable to process nil hcl object")
   183  	}
   184  
   185  	// Create an empty bundle.
   186  	res := &bundlev1.Bundle{
   187  		Labels:      input.Labels,
   188  		Annotations: input.Annotations,
   189  		Packages:    []*bundlev1.Package{},
   190  	}
   191  
   192  	for _, pkg := range input.Packages {
   193  		// Prepare a package
   194  		p := &bundlev1.Package{
   195  			Name:        pkg.Path,
   196  			Annotations: pkg.Annotations,
   197  			Labels:      pkg.Labels,
   198  			Secrets:     &bundlev1.SecretChain{},
   199  		}
   200  
   201  		// Prepare secret data
   202  		for k, v := range pkg.Secrets {
   203  			// Pack secret value
   204  			packed, err := secret.Pack(v)
   205  			if err != nil {
   206  				return nil, fmt.Errorf("unable to pack secret value for `%s`: %w", fmt.Sprintf("%s.%s", pkg.Path, k), err)
   207  			}
   208  
   209  			// Add to secret package
   210  			p.Secrets.Data = append(p.Secrets.Data, &bundlev1.KV{
   211  				Key:   k,
   212  				Type:  fmt.Sprintf("%T", v),
   213  				Value: packed,
   214  			})
   215  		}
   216  
   217  		// Add package to result
   218  		res.Packages = append(res.Packages, p)
   219  	}
   220  
   221  	// No error
   222  	return res, nil
   223  }