Skip to content

Commit

Permalink
Local source code feature (using bundle images)
Browse files Browse the repository at this point in the history
Reference shipwright-io/build#717

Add `bundle` package that contains convenience code to push a local
source code directory as a bundle image to a container registry.

Add new flags for container image and local directory settings.
  • Loading branch information
HeavyWombat committed Jul 20, 2021
1 parent 6a99070 commit dacd79f
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 11 deletions.
81 changes: 81 additions & 0 deletions pkg/shp/bundle/bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package bundle

import (
"context"
"fmt"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
progressbar "github.com/schollz/progressbar/v3"
"github.com/shipwright-io/build/pkg/reconciler/buildrun/resources/sources"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

// Push bundles the provided local directory into a container image and pushes
// it to the given registry.
func Push(ctx context.Context, io *genericclioptions.IOStreams, localDirectory string, targetImage string) error {
tag, err := name.NewTag(targetImage)
if err != nil {
return err
}

auth, err := authn.DefaultKeychain.Resolve(tag.Context())
if err != nil {
return err
}

updates := make(chan v1.Update, 1)
done := make(chan struct{}, 1)
go func() {
var progress *progressbar.ProgressBar
for {
select {
case <-ctx.Done():
return

case <-done:
return

case update, ok := <-updates:
if !ok {
return
}

if progress == nil {
progress = progressbar.NewOptions(int(update.Total),
progressbar.OptionSetWriter(io.ErrOut),
progressbar.OptionEnableColorCodes(true),
progressbar.OptionShowBytes(true),
progressbar.OptionSetWidth(15),
progressbar.OptionSetPredictTime(false),
progressbar.OptionSetDescription("Uploading local source..."),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "[green]=[reset]",
SaucerHead: "[green]>[reset]",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]"}),
progressbar.OptionClearOnFinish(),
)
defer progress.Close()
}

progress.ChangeMax64(update.Total)
_ = progress.Set64(update.Complete)
}
}
}()

fmt.Fprintf(io.Out, "Bundling %q as %q ...\n", localDirectory, targetImage)
_, err = sources.Bundle(
tag,
localDirectory,
remote.WithAuth(auth),
remote.WithProgress(updates),
)

done <- struct{}{}
return err
}
16 changes: 13 additions & 3 deletions pkg/shp/cmd/build/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ func (c *CreateCommand) Validate() error {
if c.name == "" {
return fmt.Errorf("name must be provided")
}

if c.buildSpec.Source.URL != "" &&
c.buildSpec.Source.Container != nil &&
c.buildSpec.Source.Container.Image != "" {
return fmt.Errorf("both source URL and container image are specified, only one can be used at the same time")
}

if c.buildSpec.Source.URL == "" &&
c.buildSpec.Source.Container != nil &&
c.buildSpec.Source.Container.Image == "" {
return fmt.Errorf("no input source was specified, either source URL or container image needs to be provided")
}

return nil
}

Expand Down Expand Up @@ -74,9 +87,6 @@ func createCmd() runner.SubCommand {
// instantiating command-line flags and the build-spec structure which receives the informed flag
// values, also marking certain flags as mandatory
buildSpecFlags := flags.BuildSpecFromFlags(cmd.Flags())
if err := cmd.MarkFlagRequired(flags.SourceURLFlag); err != nil {
panic(err)
}
if err := cmd.MarkFlagRequired(flags.OutputImageFlag); err != nil {
panic(err)
}
Expand Down
26 changes: 22 additions & 4 deletions pkg/shp/cmd/buildrun/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"

buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
"github.com/shipwright-io/cli/pkg/shp/bundle"
"github.com/shipwright-io/cli/pkg/shp/cmd/runner"
"github.com/shipwright-io/cli/pkg/shp/flags"
"github.com/shipwright-io/cli/pkg/shp/params"
Expand All @@ -18,8 +19,9 @@ import (
type CreateCommand struct {
cmd *cobra.Command // cobra command instance

name string // buildrun name
buildRunSpec *buildv1alpha1.BuildRunSpec // stores command-line flags
name string // buildrun name
localSourceDir *string // path to local source code directory
buildRunSpec *buildv1alpha1.BuildRunSpec // stores command-line flags
}

const buildRunCreateLongDesc = `
Expand Down Expand Up @@ -73,10 +75,25 @@ func (c *CreateCommand) Run(params *params.Params, ioStreams *genericclioptions.
br := &buildv1alpha1.BuildRun{Spec: *c.buildRunSpec}
flags.SanitizeBuildRunSpec(&br.Spec)

var build = buildv1alpha1.Build{}
if err := resource.GetBuildResource(params).Get(c.cmd.Context(), c.buildRunSpec.BuildRef.Name, &build); err != nil {
return fmt.Errorf("failed to get referenced build %q: %w", c.buildRunSpec.BuildRef.Name, err)
}

// Local source code mode:
// Make sure to bundle the configured local source directory into an image
// bundle and push it to the provided target registry.
if build.Spec.Source.Container.Image != "" {
if err := bundle.Push(c.cmd.Context(), ioStreams, *c.localSourceDir, build.Spec.Source.Container.Image); err != nil {
return err
}
}

buildRunResource := resource.GetBuildRunResource(params)
if err := buildRunResource.Create(c.cmd.Context(), c.name, br); err != nil {
return err
}

fmt.Fprintf(ioStreams.Out, "BuildRun created %q for Build %q\n", c.name, br.Spec.BuildRef.Name)
return nil
}
Expand All @@ -98,7 +115,8 @@ func createCmd() runner.SubCommand {
}

return &CreateCommand{
cmd: cmd,
buildRunSpec: buildRunSpecFlags,
cmd: cmd,
localSourceDir: flags.BundleLocalSourceFlag(cmd.Flags()),
buildRunSpec: buildRunSpecFlags,
}
}
22 changes: 22 additions & 0 deletions pkg/shp/flags/build.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package flags

import (
"os"

buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
"github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1"
Expand All @@ -16,6 +18,7 @@ func BuildSpecFromFlags(flags *pflag.FlagSet) *buildv1alpha1.BuildSpec {
Credentials: &corev1.LocalObjectReference{},
Revision: pointer.String(""),
ContextDir: pointer.String(""),
Container: &buildv1alpha1.Container{},
},
Strategy: &buildv1alpha1.Strategy{
Kind: &clusterBuildStrategyKind,
Expand Down Expand Up @@ -49,6 +52,11 @@ func SanitizeBuildSpec(b *buildv1alpha1.BuildSpec) {
if b.Source.Credentials != nil && b.Source.Credentials.Name == "" {
b.Source.Credentials = nil
}

if b.Source.Container != nil && b.Source.Container.Image == "" {
b.Source.Container = nil
}

if b.Builder != nil {
if b.Builder.Credentials != nil && b.Builder.Credentials.Name == "" {
b.Builder.Credentials = nil
Expand All @@ -58,3 +66,17 @@ func SanitizeBuildSpec(b *buildv1alpha1.BuildSpec) {
}
}
}

// BundleLocalSourceFlag returns the source directory setting based on command-line flags.
func BundleLocalSourceFlag(flags *pflag.FlagSet) (result *string) {
cwd, err := os.Getwd()
if err != nil {
cwd = "."
}

return flags.String(
"source-directory",
cwd,
"directory to be used for local source code",
)
}
8 changes: 4 additions & 4 deletions pkg/shp/flags/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import (
func TestBuildSpecFromFlags(t *testing.T) {
g := gomega.NewGomegaWithT(t)

credentials := corev1.LocalObjectReference{Name: "name"}
buildStrategyKind := buildv1alpha1.ClusterBuildStrategyKind
expected := &buildv1alpha1.BuildSpec{
Source: buildv1alpha1.Source{
Credentials: &credentials,
Credentials: &corev1.LocalObjectReference{Name: "source-credentials"},
URL: "https://some.url",
Revision: pointer.String("some-rev"),
ContextDir: pointer.String("some-contextdir"),
Container: &buildv1alpha1.Container{},
},
Strategy: &buildv1alpha1.Strategy{
Name: "strategy-name",
Expand All @@ -33,11 +33,11 @@ func TestBuildSpecFromFlags(t *testing.T) {
},
Dockerfile: pointer.String("some-dockerfile"),
Builder: &buildv1alpha1.Image{
Credentials: &credentials,
Credentials: &corev1.LocalObjectReference{Name: "builder-credentials"},
Image: "builder-image",
},
Output: buildv1alpha1.Image{
Credentials: &credentials,
Credentials: &corev1.LocalObjectReference{Name: "output-credentials"},
Image: "output-image",
},
Timeout: &metav1.Duration{
Expand Down
8 changes: 8 additions & 0 deletions pkg/shp/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const (
SourceURLFlag = "source-url"
// SourceRevisionFlag command-line flag.
SourceRevisionFlag = "source-revision"
// SourceContainerImageFlag command-line flag.
SourceContainerImageFlag = "source-bundle-image"
// SourceContextDirFlag command-line flag.
SourceContextDirFlag = "source-context-dir"
// SourceCredentialsSecretFlag command-line flag.
Expand Down Expand Up @@ -58,6 +60,12 @@ func sourceFlags(flags *pflag.FlagSet, source *buildv1alpha1.Source) {
"",
"git repository source revision",
)
flags.StringVar(
&source.Container.Image,
SourceContainerImageFlag,
"",
"source code bundle image",
)
flags.StringVar(
source.ContextDir,
SourceContextDirFlag,
Expand Down

0 comments on commit dacd79f

Please sign in to comment.