Skip to content

Commit

Permalink
Representing Shipwright resources as flags.
Browse files Browse the repository at this point in the history
Extending `flags` package to support Build and BuildRun objects, reusing
shared flags among resources. Adding more tests and documentation.
  • Loading branch information
otaviof committed Jun 7, 2021
1 parent 2250a00 commit 13f17da
Show file tree
Hide file tree
Showing 8 changed files with 410 additions and 67 deletions.
64 changes: 64 additions & 0 deletions pkg/shp/flags/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package flags

import (
buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
"github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// BuildSpecFlags creates a BuildSpec instance based on command-line flags.
func BuildSpecFlags(flags *pflag.FlagSet) *buildv1alpha1.BuildSpec {
clusterBuildStrategyKind := buildv1alpha1.ClusterBuildStrategyKind
spec := &buildv1alpha1.BuildSpec{
Source: buildv1alpha1.Source{
Credentials: &corev1.LocalObjectReference{},
},
Strategy: &buildv1alpha1.Strategy{
Kind: &clusterBuildStrategyKind,
APIVersion: buildv1alpha1.SchemeGroupVersion.Version,
},
Dockerfile: nil,
Builder: &buildv1alpha1.Image{
Credentials: &corev1.LocalObjectReference{},
},
Output: buildv1alpha1.Image{
Credentials: &corev1.LocalObjectReference{},
},
Timeout: &metav1.Duration{},
}

sourceFlags(flags, &spec.Source)
strategyFlags(flags, spec.Strategy)

flags.Var(
NewStringPointerValue(spec.Dockerfile),
"dockerfile",
"path to dockerfile relative to repository",
)

imageFlags(flags, "builder", spec.Builder)
imageFlags(flags, "output", &spec.Output)

timeoutFlags(flags, spec.Timeout)

return spec
}

// SanitizeBuildSpec checks for empty inner data structure and replaces them with nil.
func SanitizeBuildSpec(b *buildv1alpha1.BuildSpec) {
if b == nil {
return
}
if b.Source.Credentials != nil && b.Source.Credentials.Name == "" {
b.Source.Credentials = nil
}
if b.Builder != nil {
if b.Builder.Credentials != nil && b.Builder.Credentials.Name == "" {
b.Builder.Credentials = nil
}
if b.Builder.Image == "" && b.Builder.Credentials == nil {
b.Builder = nil
}
}
}
62 changes: 62 additions & 0 deletions pkg/shp/flags/build_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package flags

import (
"testing"

"github.com/onsi/gomega"
o "github.com/onsi/gomega"
buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
v1 "k8s.io/api/core/v1"
)

func TestSanitizeBuildSpec(t *testing.T) {
g := gomega.NewGomegaWithT(t)

completeBuildSpec := buildv1alpha1.BuildSpec{
Source: buildv1alpha1.Source{
Credentials: &v1.LocalObjectReference{Name: "name"},
},
Builder: &buildv1alpha1.Image{
Credentials: &v1.LocalObjectReference{Name: "name"},
Image: "image",
},
}

testCases := []struct {
name string
in buildv1alpha1.BuildSpec
out buildv1alpha1.BuildSpec
}{{
name: "all empty should stay empty",
in: buildv1alpha1.BuildSpec{},
out: buildv1alpha1.BuildSpec{},
}, {
name: "should clean-up `.spec.source.credentials`",
in: buildv1alpha1.BuildSpec{Source: buildv1alpha1.Source{
Credentials: &v1.LocalObjectReference{},
}},
out: buildv1alpha1.BuildSpec{},
}, {
name: "should clean-up `.spec.builder.credentials`",
in: buildv1alpha1.BuildSpec{Builder: &buildv1alpha1.Image{
Credentials: &v1.LocalObjectReference{},
}},
out: buildv1alpha1.BuildSpec{},
}, {
name: "should clean-up `.spec.builder.image`",
in: buildv1alpha1.BuildSpec{Builder: &buildv1alpha1.Image{}},
out: buildv1alpha1.BuildSpec{},
}, {
name: "should not clean-up complete objects",
in: completeBuildSpec,
out: completeBuildSpec,
}}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
copy := tt.in.DeepCopy()
SanitizeBuildSpec(copy)
g.Expect(tt.out).To(o.Equal(*copy))
})
}
}
87 changes: 21 additions & 66 deletions pkg/shp/flags/buildrun.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package flags

import (
"time"

buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
"github.com/spf13/pflag"

Expand All @@ -12,10 +10,9 @@ import (

// BuildRunSpecFlags BuildRun's spec represtantation as command-line flags.
func BuildRunSpecFlags(flags *pflag.FlagSet) *buildv1alpha1.BuildRunSpec {
empty := ""
spec := &buildv1alpha1.BuildRunSpec{
BuildRef: &buildv1alpha1.BuildRef{},
ServiceAccount: &buildv1alpha1.ServiceAccount{Name: &empty},
ServiceAccount: &buildv1alpha1.ServiceAccount{},
Timeout: &metav1.Duration{},
Output: &buildv1alpha1.Image{
Credentials: &corev1.LocalObjectReference{},
Expand All @@ -25,70 +22,28 @@ func BuildRunSpecFlags(flags *pflag.FlagSet) *buildv1alpha1.BuildRunSpec {
buildRefFlags(flags, spec.BuildRef)
serviceAccountFlags(flags, spec.ServiceAccount)
timeoutFlags(flags, spec.Timeout)
outputFlags(flags, spec.Output)
imageFlags(flags, "output", spec.Output)

return spec
}

// buildRefFlags register flags for BuildRun's spec.buildRef attribute.
func buildRefFlags(flags *pflag.FlagSet, buildRef *buildv1alpha1.BuildRef) {
flags.StringVar(
&buildRef.Name,
"buildref-name",
"",
"name of build resource to reference",
)
flags.StringVar(
&buildRef.APIVersion,
"buildref-apiversion",
"",
"API version of build resource to reference",
)
}

// serviceAccountFlags register flags for BuildRun's spec.serviceAccount attribute.
func serviceAccountFlags(flags *pflag.FlagSet, sa *buildv1alpha1.ServiceAccount) {
flags.StringVar(
sa.Name,
"sa-name",
"",
"service-account name",
)
flags.BoolVar(
&sa.Generate,
"sa-generate",
false,
"generate a service-account for the build",
)
}

// serviceAccountFlags register flags for BuildRun's spec.timeout attribute.
func timeoutFlags(flags *pflag.FlagSet, timeout *metav1.Duration) {
flags.DurationVar(
&timeout.Duration,
"timeout",
time.Duration(0),
"build process timeout",
)
}

// serviceAccountFlags register flags for BuildRun's spec.output attribute.
func outputFlags(flags *pflag.FlagSet, output *buildv1alpha1.Image) {
flags.StringVar(
&output.Image,
"output-image",
"",
"output image URL",
)
secretRefFlags(flags, output.Credentials)
}

// serviceAccountFlags register flags for BuildRun's spec.output.credentials attribute.
func secretRefFlags(flags *pflag.FlagSet, secretRef *corev1.LocalObjectReference) {
flags.StringVar(
&secretRef.Name,
"output-credentials",
"",
"Kubernetes Secret with credentials for output container registry.",
)
// SanitizeBuildRunSpec when inner elements are empty, making sure they are replace by a nil pointer.
func SanitizeBuildRunSpec(br *buildv1alpha1.BuildRunSpec) {
if br == nil {
return
}
if br.ServiceAccount != nil {
if (br.ServiceAccount.Name == nil || *br.ServiceAccount.Name == "") &&
br.ServiceAccount.Generate == false {
br.ServiceAccount = nil
}
}
if br.Output != nil {
if br.Output.Credentials != nil && br.Output.Credentials.Name == "" {
br.Output.Credentials = nil
}
if br.Output.Image == "" && br.Output.Credentials == nil {
br.Output = nil
}
}
}
54 changes: 54 additions & 0 deletions pkg/shp/flags/buildrun_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package flags

import (
"testing"

"github.com/onsi/gomega"
buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
v1 "k8s.io/api/core/v1"

o "github.com/onsi/gomega"
)

func TestSanitizeBuildRunSpec(t *testing.T) {
g := gomega.NewGomegaWithT(t)

name := "name"
completeBuildRunSpec := buildv1alpha1.BuildRunSpec{
ServiceAccount: &buildv1alpha1.ServiceAccount{Name: &name},
Output: &buildv1alpha1.Image{
Credentials: &v1.LocalObjectReference{Name: "name"},
Image: "image",
},
}

testCases := []struct {
name string
in buildv1alpha1.BuildRunSpec
out buildv1alpha1.BuildRunSpec
}{{
name: "all empty should stay empty",
in: buildv1alpha1.BuildRunSpec{},
out: buildv1alpha1.BuildRunSpec{},
}, {
name: "should clean-up service-account",
in: buildv1alpha1.BuildRunSpec{ServiceAccount: &buildv1alpha1.ServiceAccount{}},
out: buildv1alpha1.BuildRunSpec{},
}, {
name: "should clean-up output",
in: buildv1alpha1.BuildRunSpec{Output: &buildv1alpha1.Image{}},
out: buildv1alpha1.BuildRunSpec{},
}, {
name: "should not clean-up complete objects",
in: completeBuildRunSpec,
out: completeBuildRunSpec,
}}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
copy := tt.in.DeepCopy()
SanitizeBuildRunSpec(copy)
g.Expect(tt.out).To(o.Equal(*copy))
})
}
}
14 changes: 13 additions & 1 deletion pkg/shp/flags/doc.go
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
// Package flags contains command-line flags that can be reused over the project.
// Package flags contains command-line flags that can be reused over the project, taking real
// Shipwright Build-Controller resources as a direct representation as command-line flags.
//
// For instance:
//
// cmd := &cobra.Command{}
// br := flags.BuildRunSpecFlags(cmd.Flags())
// flags.SanitizeBuildRunSpec(&br.Spec)
//
// The snippet above shows how to decorate an existing cobra.Command instance with flags, and return
// an instantiated object, which will be receive the inputted values. And, to make sure inner items
// are set to nil when all empty, to the resource in question can be used directly against Kubernetes
// API.
package flags
Loading

0 comments on commit 13f17da

Please sign in to comment.