github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/amazoneks/render.go (about)

     1  package amazoneks
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"path"
     8  	"text/template"
     9  
    10  	"github.com/go-kit/kit/log"
    11  	multierror "github.com/hashicorp/go-multierror"
    12  	"github.com/pkg/errors"
    13  	"github.com/replicatedhq/libyaml"
    14  	"github.com/replicatedhq/ship/pkg/api"
    15  	"github.com/replicatedhq/ship/pkg/lifecycle/render/inline"
    16  	"github.com/replicatedhq/ship/pkg/lifecycle/render/root"
    17  	"github.com/replicatedhq/ship/pkg/templates"
    18  	"github.com/spf13/afero"
    19  )
    20  
    21  // Renderer is something that can render a terraform asset (that produces an EKS cluster) as part of a planner.Plan
    22  type Renderer interface {
    23  	Execute(
    24  		rootFs root.Fs,
    25  		asset api.EKSAsset,
    26  		meta api.ReleaseMetadata,
    27  		templateContext map[string]interface{},
    28  		configGroups []libyaml.ConfigGroup,
    29  	) func(ctx context.Context) error
    30  }
    31  
    32  // LocalRenderer renders a terraform asset by writing generated terraform source code
    33  type LocalRenderer struct {
    34  	BuilderBuilder *templates.BuilderBuilder
    35  	Fs             afero.Afero
    36  	Inline         inline.Renderer
    37  	Logger         log.Logger
    38  }
    39  
    40  var _ Renderer = &LocalRenderer{}
    41  
    42  func NewRenderer(
    43  	bb *templates.BuilderBuilder,
    44  	fs afero.Afero,
    45  	inline inline.Renderer,
    46  	logger log.Logger,
    47  ) Renderer {
    48  	return &LocalRenderer{
    49  		BuilderBuilder: bb,
    50  		Fs:             fs,
    51  		Inline:         inline,
    52  		Logger:         logger,
    53  	}
    54  }
    55  
    56  func (r *LocalRenderer) Execute(
    57  	rootFs root.Fs,
    58  	asset api.EKSAsset,
    59  	meta api.ReleaseMetadata,
    60  	templateContext map[string]interface{},
    61  	configGroups []libyaml.ConfigGroup,
    62  ) func(ctx context.Context) error {
    63  	return func(ctx context.Context) error {
    64  
    65  		builder, err := r.BuilderBuilder.FullBuilder(meta, configGroups, templateContext)
    66  		if err != nil {
    67  			return errors.Wrap(err, "init builder")
    68  		}
    69  
    70  		asset, err = buildAsset(asset, builder)
    71  		if err != nil {
    72  			return errors.Wrap(err, "build asset")
    73  		}
    74  
    75  		contents, err := renderTerraformContents(asset)
    76  		if err != nil {
    77  			return errors.Wrap(err, "render tf config")
    78  		}
    79  
    80  		assetsPath := "amazon_eks.tf"
    81  		if asset.Dest != "" {
    82  			assetsPath = asset.Dest
    83  		}
    84  
    85  		// save the path to the kubeconfig that running the generated terraform will produce
    86  		templates.AddAmazonEKSPath(asset.ClusterName,
    87  			path.Join(path.Dir(assetsPath), "kubeconfig_"+asset.ClusterName))
    88  
    89  		// write the inline spec
    90  		err = r.Inline.Execute(
    91  			rootFs,
    92  			api.InlineAsset{
    93  				Contents: contents,
    94  				AssetShared: api.AssetShared{
    95  					Dest: assetsPath,
    96  					Mode: asset.Mode,
    97  				},
    98  			},
    99  			meta,
   100  			templateContext,
   101  			configGroups,
   102  		)(ctx)
   103  
   104  		if err != nil {
   105  			return errors.Wrap(err, "write tf config")
   106  		}
   107  		return nil
   108  	}
   109  }
   110  
   111  func buildAsset(asset api.EKSAsset, builder *templates.Builder) (api.EKSAsset, error) {
   112  	var err error
   113  	var multiErr *multierror.Error
   114  
   115  	asset.ClusterName, err = builder.String(asset.ClusterName)
   116  	multiErr = multierror.Append(multiErr, errors.Wrap(err, "build cluster_name"))
   117  
   118  	asset.Region, err = builder.String(asset.Region)
   119  	multiErr = multierror.Append(multiErr, errors.Wrap(err, "build region"))
   120  
   121  	// build created vpc
   122  	if asset.CreatedVPC != nil {
   123  		asset.CreatedVPC.VPCCIDR, err = builder.String(asset.CreatedVPC.VPCCIDR)
   124  		multiErr = multierror.Append(multiErr, errors.Wrap(err, "build vpc_cidr"))
   125  
   126  		for idx, zone := range asset.CreatedVPC.Zones {
   127  			asset.CreatedVPC.Zones[idx], err = builder.String(zone)
   128  			multiErr = multierror.Append(multiErr, errors.Wrap(err, fmt.Sprintf("build vpc zone %d", idx)))
   129  		}
   130  		for idx, subnet := range asset.CreatedVPC.PublicSubnets {
   131  			asset.CreatedVPC.PublicSubnets[idx], err = builder.String(subnet)
   132  			multiErr = multierror.Append(multiErr, errors.Wrap(err, fmt.Sprintf("build vpc public subnet %d", idx)))
   133  		}
   134  		for idx, subnet := range asset.CreatedVPC.PrivateSubnets {
   135  			asset.CreatedVPC.PrivateSubnets[idx], err = builder.String(subnet)
   136  			multiErr = multierror.Append(multiErr, errors.Wrap(err, fmt.Sprintf("build vpc private subnet zone %d", idx)))
   137  		}
   138  	}
   139  
   140  	// build existing vpc
   141  	if asset.ExistingVPC != nil {
   142  		asset.ExistingVPC.VPCID, err = builder.String(asset.ExistingVPC.VPCID)
   143  		multiErr = multierror.Append(multiErr, errors.Wrap(err, "build vpc_id"))
   144  
   145  		for idx, subnet := range asset.ExistingVPC.PublicSubnets {
   146  			asset.ExistingVPC.PublicSubnets[idx], err = builder.String(subnet)
   147  			multiErr = multierror.Append(multiErr, errors.Wrap(err, fmt.Sprintf("build vpc public subnet %d", idx)))
   148  		}
   149  		for idx, subnet := range asset.ExistingVPC.PrivateSubnets {
   150  			asset.ExistingVPC.PrivateSubnets[idx], err = builder.String(subnet)
   151  			multiErr = multierror.Append(multiErr, errors.Wrap(err, fmt.Sprintf("build vpc private subnet zone %d", idx)))
   152  		}
   153  	}
   154  
   155  	// build autoscaling groups
   156  	for idx, group := range asset.AutoscalingGroups {
   157  		asset.AutoscalingGroups[idx].Name, err = builder.String(group.Name)
   158  		multiErr = multierror.Append(multiErr, errors.Wrap(err, fmt.Sprintf("build autoscaling group %d name", idx)))
   159  
   160  		asset.AutoscalingGroups[idx].GroupSize, err = builder.String(group.GroupSize)
   161  		multiErr = multierror.Append(multiErr, errors.Wrap(err, fmt.Sprintf("build autoscaling group %d group_size", idx)))
   162  
   163  		asset.AutoscalingGroups[idx].MachineType, err = builder.String(group.MachineType)
   164  		multiErr = multierror.Append(multiErr, errors.Wrap(err, fmt.Sprintf("build autoscaling group %d machine_type", idx)))
   165  	}
   166  
   167  	return asset, multiErr.ErrorOrNil()
   168  }
   169  
   170  func renderTerraformContents(asset api.EKSAsset) (string, error) {
   171  	templateString := ""
   172  	if asset.CreatedVPC != nil {
   173  		templateString = newVPCTempl
   174  	} else if asset.ExistingVPC != nil {
   175  		templateString = existingVPCTempl
   176  	} else {
   177  		return "", errors.New("a created or existing VPC must be provided")
   178  	}
   179  
   180  	templateString += workerTempl
   181  	t, err := template.New("eksTemplate").Parse(templateString)
   182  	if err != nil {
   183  		return "", err
   184  	}
   185  	return executeTemplate(t, asset)
   186  }
   187  
   188  func executeTemplate(t *template.Template, asset api.EKSAsset) (string, error) {
   189  	var tpl bytes.Buffer
   190  	if err := t.Execute(&tpl, asset); err != nil {
   191  		return "", err
   192  	}
   193  
   194  	return tpl.String(), nil
   195  }