github.com/emcfarlane/larking@v0.0.0-20220605172417-1704b45ee6c3/starlib/starlarkrule/container.go (about)

     1  package starlarkrule
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	"github.com/containerd/stargz-snapshotter/estargz"
     8  	"github.com/emcfarlane/larking/starlib/starext"
     9  	"github.com/emcfarlane/larking/starlib/starlarkstruct"
    10  	"github.com/emcfarlane/larking/starlib/starlarkthread"
    11  	"github.com/google/go-containerregistry/pkg/authn"
    12  	cname "github.com/google/go-containerregistry/pkg/name"
    13  	v1 "github.com/google/go-containerregistry/pkg/v1"
    14  	"github.com/google/go-containerregistry/pkg/v1/empty"
    15  	"github.com/google/go-containerregistry/pkg/v1/mutate"
    16  	"github.com/google/go-containerregistry/pkg/v1/remote"
    17  	"github.com/google/go-containerregistry/pkg/v1/tarball"
    18  	"go.starlark.net/starlark"
    19  )
    20  
    21  // conatiner rules implemented with go-containerregistry.
    22  // Based on:
    23  // https://github.com/google/ko/blob/main/pkg/build/gobuild.go
    24  // https://github.com/bazelbuild/rules_docker/tree/master/container
    25  var containerModule = &starlarkstruct.Module{
    26  	Name: "container",
    27  	Members: starlark.StringDict{
    28  		"pull":  starext.MakeBuiltin("container.pull", containerPull),
    29  		"build": starext.MakeBuiltin("container.build", containerBuild),
    30  		"push":  starext.MakeBuiltin("container.push", containerPush),
    31  	},
    32  }
    33  
    34  const ImageConstructor starlark.String = "container.image"
    35  
    36  // TODO: return starlark provider.
    37  func NewContainerImage(filename, reference string) starlark.Value {
    38  	return starlarkstruct.FromStringDict(ImageConstructor, map[string]starlark.Value{
    39  		"name":      starlark.String(filename),
    40  		"reference": starlark.String(reference),
    41  	})
    42  }
    43  
    44  func containerPull(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    45  	var (
    46  		name      string
    47  		reference string
    48  	)
    49  	if err := starlark.UnpackArgs(
    50  		fnname, args, kwargs,
    51  		"name", &name, "reference", &reference,
    52  	); err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	ref, err := cname.ParseReference(reference)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	ref.Context()
    61  
    62  	ctx := starlarkthread.GetContext(thread)
    63  
    64  	fmt.Println("image?")
    65  	img, err := remote.Image(ref,
    66  		remote.WithAuthFromKeychain(authn.DefaultKeychain),
    67  		remote.WithContext(ctx),
    68  	)
    69  	if err != nil {
    70  		fmt.Println("image?", err)
    71  		return nil, err
    72  	}
    73  
    74  	l, err := ParseRelativeLabel(thread.Name, name)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	fmt.Println("HERE?", l)
    79  
    80  	blobs := starext.Blobs{}
    81  	defer blobs.Close()
    82  
    83  	// TODO: caching.
    84  	// HACK: check we have hash.
    85  	var rebuild = true
    86  
    87  	if rebuild {
    88  		w, err := blobs.NewWriter(ctx, l.BucketURL(), l.Key(), nil)
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  		defer w.Close()
    93  
    94  		if err := tarball.Write(ref, img, w); err != nil {
    95  			return nil, err
    96  		}
    97  	}
    98  	fmt.Println("HERE??", l)
    99  	return l, nil
   100  }
   101  
   102  func listToStrings(l *starlark.List) ([]string, error) {
   103  	iter := l.Iterate()
   104  	defer iter.Done()
   105  
   106  	var ss []string
   107  	var x starlark.Value
   108  	for iter.Next(&x) {
   109  		s, ok := starlark.AsString(x)
   110  		if !ok {
   111  			return nil, fmt.Errorf("invalid string list")
   112  		}
   113  		ss = append(ss, s)
   114  	}
   115  	return ss, nil
   116  }
   117  
   118  func containerInfo(args *AttrArgs) (src *Label, ref string, err error) {
   119  	srcValue, err := args.Attr("src")
   120  	if err != nil {
   121  		return nil, "", err
   122  	}
   123  	src, ok := srcValue.(*Label)
   124  	if !ok {
   125  		return nil, "", fmt.Errorf("invalid source")
   126  	}
   127  
   128  	refValue, err := args.Attr("reference")
   129  	if err != nil {
   130  		return nil, "", err
   131  	}
   132  	ref, ok = starlark.AsString(refValue)
   133  	if !ok {
   134  		return nil, "", fmt.Errorf("invalid reference")
   135  	}
   136  	return src, ref, nil
   137  }
   138  
   139  func containerBuild(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   140  	ctx := starlarkthread.GetContext(thread)
   141  
   142  	var (
   143  		name            string
   144  		entrypointList  *starlark.List
   145  		tar             *Label
   146  		base            *AttrArgs //*Label
   147  		prioritizedList *starlark.List
   148  		reference       string
   149  	)
   150  	if err := starlark.UnpackArgs(
   151  		fnname, args, kwargs,
   152  		"name", &name,
   153  		"entrypoint", &entrypointList,
   154  		"tar", &tar,
   155  		"reference", &reference,
   156  		"base?", &base,
   157  		"prioritized_files?", &prioritizedList,
   158  	); err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	// TODO: load tag from provider?
   163  	entrypoint, err := listToStrings(entrypointList)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	prioritizedFiles, err := listToStrings(prioritizedList)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	blobs := starext.Blobs{}
   173  	defer blobs.Close()
   174  
   175  	baseImage := empty.Image
   176  	if base != nil {
   177  		src, reference, err := containerInfo(base)
   178  		if err != nil {
   179  			return nil, err
   180  		}
   181  
   182  		//// Load base image from local.
   183  		//imageProvider, err := toStruct(base, ImageConstructor)
   184  		//if err != nil {
   185  		//	return nil, fmt.Errorf("image provider: %w", err)
   186  		//}
   187  		//if err := assertConstructor(imageProvider, ImageConstructor); err != nil {
   188  		//	return nil, err
   189  		//}
   190  
   191  		//filename, err := getAttrStr(imageProvider, "name")
   192  		//if err != nil {
   193  		//	return nil, err
   194  		//}
   195  
   196  		//reference, err := getAttrStr(imageProvider, "reference")
   197  		//if err != nil {
   198  		//	return nil, err
   199  		//}
   200  
   201  		tag, err := cname.NewTag(reference, cname.StrictValidation)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  
   206  		opener := func() (io.ReadCloser, error) {
   207  			return blobs.NewReader(ctx, src.BucketURL(), src.Key(), nil)
   208  		}
   209  
   210  		// Load base from filesystem.
   211  		img, err := tarball.Image(opener, &tag)
   212  		if err != nil {
   213  			return nil, err
   214  		}
   215  		baseImage = img
   216  	}
   217  
   218  	var layers []mutate.Addendum
   219  
   220  	//tarStruct, err := toStruct(tar, FileConstructor)
   221  	//if err != nil {
   222  	//	return nil, err
   223  	//}
   224  	//tarFilename, err := getAttrStr(tarStruct, "path")
   225  	//if err != nil {
   226  	//	return nil, err
   227  	//}
   228  
   229  	r, err := blobs.NewReader(ctx, tar.BucketURL(), tar.Key(), nil)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	defer r.Close()
   234  
   235  	imageLayer, err := tarball.LayerFromReader(
   236  		r, tarball.WithCompressedCaching,
   237  		tarball.WithEstargzOptions(estargz.WithPrioritizedFiles(
   238  			// When using estargz, prioritize downloading the binary entrypoint.
   239  			prioritizedFiles,
   240  		)),
   241  	)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	layers = append(layers, mutate.Addendum{
   246  		Layer: imageLayer,
   247  		History: v1.History{
   248  			Author:    "laze",
   249  			CreatedBy: "laze " + name,
   250  			Comment:   "ship it real good",
   251  		},
   252  	})
   253  
   254  	// Augment the base image with our application layer.
   255  	appImage, err := mutate.Append(baseImage, layers...)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	// Start from a copy of the base image's config file, and set
   261  	// the entrypoint to our app.
   262  	cfg, err := appImage.ConfigFile()
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	cfg = cfg.DeepCopy()
   267  	cfg.Config.Entrypoint = entrypoint
   268  	//updatePath(cfg)
   269  	cfg.Config.Env = append(cfg.Config.Env, "LAZE_DATA_PATH="+"/") // TODO
   270  	cfg.Author = "github.com/emcfarlane/laze"
   271  
   272  	if cfg.Config.Labels == nil {
   273  		cfg.Config.Labels = map[string]string{}
   274  	}
   275  	// TODO: Add support for labels.
   276  	//for k, v := range labels {
   277  	//	cfg.Config.Labels[k] = v
   278  	//}
   279  
   280  	img, err := mutate.ConfigFile(appImage, cfg)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	//empty := v1.Time{}
   286  	//if g.creationTime != empty {
   287  	//	return mutate.CreatedAt(image, g.creationTime)
   288  	//}
   289  
   290  	l, err := ParseRelativeLabel(thread.Name, name)
   291  	if err != nil {
   292  		return nil, err
   293  	}
   294  
   295  	w, err := blobs.NewWriter(ctx, l.BucketURL(), l.Key(), nil)
   296  	if err != nil {
   297  		return nil, err
   298  	}
   299  	defer w.Close()
   300  
   301  	//filename := "" // c.key
   302  	//f, err := os.Create(filename)
   303  	//if err != nil {
   304  	//	panic(err)
   305  	//}
   306  	//defer f.Close()
   307  
   308  	ref, err := cname.ParseReference(reference)
   309  	if err != nil {
   310  		return nil, err
   311  	}
   312  
   313  	if err := tarball.Write(ref, img, w); err != nil {
   314  		return nil, err
   315  	}
   316  	return l, nil
   317  	//return NewContainerImage(filename, reference), nil
   318  }
   319  
   320  func containerPush(thread *starlark.Thread, fnname string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   321  	fmt.Println("RUNNING CONTAINER PUSH")
   322  	ctx := starlarkthread.GetContext(thread)
   323  	var (
   324  		name      string
   325  		image     *AttrArgs
   326  		reference string
   327  	)
   328  	if err := starlark.UnpackArgs(
   329  		fnname, args, kwargs,
   330  		"name", &name,
   331  		"image", &image,
   332  		"reference", &reference,
   333  	); err != nil {
   334  		fmt.Println("failed on starlark")
   335  		return nil, err
   336  	}
   337  
   338  	src, reference, err := containerInfo(image)
   339  	if err != nil {
   340  		return nil, err
   341  	}
   342  
   343  	//imageProvider, err := toStruct(image, ImageConstructor)
   344  	//if err != nil {
   345  	//	return nil, fmt.Errorf("image provider: %w", err)
   346  	//}
   347  
   348  	//// TODO: should it be a file provider?
   349  	//filename, err := getAttrStr(imageProvider, "name")
   350  	//if err != nil {
   351  	//	return nil, err
   352  	//}
   353  	//imageRef, err := getAttrStr(imageProvider, "reference")
   354  	//if err != nil {
   355  	//	return nil, err
   356  	//}
   357  
   358  	tag, err := cname.NewTag(reference, cname.StrictValidation)
   359  	if err != nil {
   360  		fmt.Println("FAILED ON tag")
   361  		return nil, err
   362  	}
   363  
   364  	blobs := starext.Blobs{}
   365  	defer blobs.Close()
   366  
   367  	opener := func() (io.ReadCloser, error) {
   368  		return blobs.NewReader(ctx, src.BucketURL(), src.Key(), nil)
   369  	}
   370  
   371  	// Load base from filesystem.
   372  	img, err := tarball.Image(opener, &tag)
   373  	if err != nil {
   374  		fmt.Println("FAILED ON image load")
   375  		return nil, err
   376  	}
   377  
   378  	ref, err := cname.ParseReference(reference)
   379  	if err != nil {
   380  		fmt.Println("FAILED ON REF")
   381  		return nil, err
   382  	}
   383  
   384  	if err := remote.Write(ref, img,
   385  		remote.WithAuthFromKeychain(authn.DefaultKeychain),
   386  		remote.WithContext(ctx),
   387  	); err != nil {
   388  		fmt.Println("failing here?", err)
   389  		return nil, err
   390  	}
   391  	return src, nil
   392  	//return NewContainerImage(filename, reference), nil
   393  }