Skip to content

Commit

Permalink
support cnb versioning, clean up build envs (digitalocean#1215)
Browse files Browse the repository at this point in the history
  • Loading branch information
kamaln7 authored and nicktate committed Sep 19, 2022
1 parent 39aa068 commit c485a29
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 97 deletions.
75 changes: 40 additions & 35 deletions commands/apps_dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,13 @@ func RunAppsDevBuild(c *CmdConfig) error {

conf, err := newAppDevConfig(c)
if err != nil {
return err
return fmt.Errorf("initializing config: %w", err)
}

var spec *godo.AppSpec
var (
spec *godo.AppSpec
cnbVersioning *godo.AppBuildConfigCNBVersioning
)
appID, err := conf.GetString(doctl.ArgApp)
if err != nil {
return err
Expand All @@ -124,32 +127,30 @@ func RunAppsDevBuild(c *CmdConfig) error {
// TODO: if this is the user's first time running dev build, ask them if they'd like to
// link an existing app.
if appID != "" {
template.Print(`{{success checkmark}} fetching app details{{nl}}`, AppsDevDefaultSpecPath)
app, err := c.Apps().Get(appID)
if err != nil {
return err
}
spec = app.Spec
cnbVersioning = app.GetBuildConfig().GetCNBVersioning()
}

appSpecPath, err := conf.GetString(doctl.ArgAppSpec)
if err != nil {
return err
}

if spec == nil {
if appSpecPath == "" {
if _, err := os.Stat(AppsDevDefaultSpecPath); err == nil {
appSpecPath = AppsDevDefaultSpecPath
template.Print(heredoc.Doc(`
{{success checkmark}} using app spec at {{highlight .}}{{nl}}`,
), AppsDevDefaultSpecPath)
}
if spec == nil && appSpecPath == "" {
if _, err := os.Stat(AppsDevDefaultSpecPath); err == nil {
appSpecPath = AppsDevDefaultSpecPath
template.Print(`{{success checkmark}} using app spec at {{highlight .}}{{nl}}`, AppsDevDefaultSpecPath)
}
if appSpecPath != "" {
spec, err = readAppSpec(os.Stdin, appSpecPath)
if err != nil {
return err
}
}
if appSpecPath != "" {
spec, err = readAppSpec(os.Stdin, appSpecPath)
if err != nil {
return err
}
}

Expand All @@ -171,26 +172,31 @@ func RunAppsDevBuild(c *CmdConfig) error {
if len(c.Args) >= 1 {
component = c.Args[0]
}
if Interactive && component == "" {
if component == "" {
var components []list.Item
_ = godo.ForEachAppSpecComponent(spec, func(c godo.AppBuildableComponentSpec) error {
components = append(components, componentListItem{c})
return nil
})
list := list.New(components)
list.Model().Title = "select a component"
list.Model().SetStatusBarItemName("component", "components")
selected, err := list.Select()
if err != nil {
return err
} else if selected == nil {
return fmt.Errorf("cancelled")
}
selectedComponent, ok := selected.(componentListItem)
if !ok {
return fmt.Errorf("unexpected item type %T", selectedComponent)

if len(components) == 1 {
component = components[0].(componentListItem).spec.GetName()
} else if len(components) > 1 && Interactive {
list := list.New(components)
list.Model().Title = "select a component"
list.Model().SetStatusBarItemName("component", "components")
selected, err := list.Select()
if err != nil {
return err
} else if selected == nil {
return fmt.Errorf("cancelled")
}
selectedComponent, ok := selected.(componentListItem)
if !ok {
return fmt.Errorf("unexpected item type %T", selectedComponent)
}
component = selectedComponent.spec.GetName()
}
component = selectedComponent.spec.GetName()
}

if component == "" {
Expand All @@ -205,9 +211,10 @@ func RunAppsDevBuild(c *CmdConfig) error {
return err
}

buildingComponentLine := template.String(heredoc.Doc(`
building {{lower (snakeToTitle .GetType)}} {{highlight .GetName}}`,
), componentSpec)
buildingComponentLine := template.String(
`building {{lower (snakeToTitle .GetType)}} {{highlight .GetName}}`,
componentSpec,
)
template.Print(`{{success checkmark}} {{.}}{{nl 2}}`, buildingComponentLine)

if componentSpec.GetSourceDir() != "" {
Expand Down Expand Up @@ -240,9 +247,6 @@ func RunAppsDevBuild(c *CmdConfig) error {
if err != nil {
return err
}
if registryName == "" {
return errors.New("registry-name is required")
}

buildOverrride, err := conf.GetString(doctl.ArgAppDevBuildCommand)
if err != nil {
Expand Down Expand Up @@ -306,6 +310,7 @@ func RunAppsDevBuild(c *CmdConfig) error {
EnvOverride: envs,
BuildCommandOverride: buildOverrride,
LogWriter: logWriter,
Versioning: builder.Versioning{CNB: cnbVersioning},
})
if err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions commands/apps_dev_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func RunAppsDevConfigSet(c *CmdConfig) error {
}

for _, arg := range c.Args {
split := strings.Split(arg, "=")
split := strings.SplitN(arg, "=", 2)
if len(split) != 2 {
return errors.New("unexpected arg: " + arg)
}
Expand Down Expand Up @@ -223,7 +223,7 @@ func newAppDevConfig(cmdConfig *CmdConfig) (*appDevConfig, error) {
if err := ensureStringInFile(devConfigFilePath, ""); err != nil {
return nil, err
}
if err := ensureStringInFile(filepath.Join(configDir, ".gitignore"), "dev-config.yaml"); err != nil {
if err := ensureStringInFile(filepath.Join(configDir, ".gitignore"), DefaultDevConfigFile); err != nil {
return nil, err
}
} else if _, err := os.Stat(devConfigFilePath); err != nil {
Expand Down
4 changes: 3 additions & 1 deletion commands/charm/pager/pager.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ func WithTitle(title string) Option {

func (p *Pager) Write(b []byte) (int, error) {
n, err := p.buffer.Write(b)
p.prog.Send(msgUpdate{})
if p.prog != nil {
p.prog.Send(msgUpdate{})
}
return n, err
}

Expand Down
4 changes: 4 additions & 0 deletions commands/charm/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ func Indent(level uint) io.Writer {
return indent.NewWriterPipe(os.Stdout, level, nil)
}

func IndentWriter(w io.Writer, level uint) io.Writer {
return indent.NewWriterPipe(w, level, nil)
}

func IndentString(level uint, str string) string {
return indent.String(str, level)
}
Expand Down
70 changes: 38 additions & 32 deletions internal/apps/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"os"
"time"

"github.com/MakeNowJust/heredoc"
"github.com/digitalocean/doctl/commands/charm"
"github.com/digitalocean/doctl/commands/charm/template"
"github.com/digitalocean/godo"
)
Expand Down Expand Up @@ -46,7 +46,12 @@ type baseComponentBuilder struct {
}

func (b baseComponentBuilder) ImageOutputName() string {
return fmt.Sprintf("%s/%s:dev", b.registry, b.component.GetName())
ref := fmt.Sprintf("%s:dev", b.component.GetName())
if b.registry != "" {
ref = fmt.Sprintf("%s/%s", b.registry, ref)
}

return ref
}

func (b baseComponentBuilder) getLogWriter() io.Writer {
Expand All @@ -56,55 +61,42 @@ func (b baseComponentBuilder) getLogWriter() io.Writer {
return b.logWriter
}

func (b baseComponentBuilder) getEnvMap() map[string]string {
envs := map[string]string{}
func (b baseComponentBuilder) getEnvMap() (map[string]string, error) {
envMap := map[string]string{}
lw := b.getLogWriter()
template.Render(lw, `{{success checkmark}} configuring build environment variables{{nl}}`, nil)
subLW := charm.IndentWriter(lw, 2)

template.Render(lw, heredoc.Doc(`
{{success checkmark}} configuring build environment variables... {{nl 2}}`,
), nil)

if b.spec != nil {
for _, e := range b.spec.Envs {
addEnvs := func(envs ...*godo.AppVariableDefinition) {
for _, e := range envs {
if e.Type == godo.AppVariableType_Secret {
template.Render(lw, heredoc.Doc(`
=> Ignoring SECRET variable {{highlight .GetKey}}{{nl}}`,
), e)
template.Render(subLW, `{{success checkmark}} ignoring SECRET variable {{highlight .GetKey}}{{nl}}`, e)
continue
}
if e.Scope != godo.AppVariableScope_RunTime {
val := e.Value
envs[e.Key] = val
envMap[e.Key] = val
}
}
}

for _, e := range b.component.GetEnvs() {
if e.Type == godo.AppVariableType_Secret {
template.Render(lw, heredoc.Doc(`
=> Ignoring SECRET variable {{highlight .GetKey}}{{nl}}`,
), e)
continue
}
if e.Scope != godo.AppVariableScope_RunTime {
val := e.Value
envs[e.Key] = val
}
}
addEnvs(b.spec.GetEnvs()...)
addEnvs(b.component.GetEnvs()...)

for k, v := range b.envOverrides {
v := v
if _, ok := envs[k]; ok {
template.Render(lw, heredoc.Doc(`
=> Overwriting {{highlight .}} with provided env value{{nl}}`,
), k)
_, exists := envMap[k]
if !exists {
// TODO: if interactive prompt to auto add to spec
return nil, fmt.Errorf("variable not in found in app spec: %s", k)
}
envs[k] = v
template.Render(subLW, `{{success checkmark}} overriding value for variable {{highlight .}}{{nl}}`, k)
envMap[k] = v
}

fmt.Fprint(lw, "\n")

return envs
return envMap, nil
}

// NewBuilderOpts ...
Expand All @@ -114,6 +106,11 @@ type NewBuilderOpts struct {
EnvOverride map[string]string
BuildCommandOverride string
LogWriter io.Writer
Versioning Versioning
}

type Versioning struct {
CNB *godo.AppBuildConfigCNBVersioning
}

// DefaultComponentBuilderFactory is the standard component builder factory.
Expand Down Expand Up @@ -141,6 +138,14 @@ func (f *DefaultComponentBuilderFactory) NewComponentBuilder(cli DockerEngineCli
copyOnWriteSemantics := true

if component.GetDockerfilePath() == "" {
var cnbVersioning CNBVersioning
for _, bp := range opts.Versioning.CNB.GetBuildpacks() {
cnbVersioning.Buildpacks = append(cnbVersioning.Buildpacks, &Buildpack{
ID: bp.ID,
Version: fmt.Sprintf("%d.0.0", bp.MajorVersion),
})
}

return &CNBComponentBuilder{
baseComponentBuilder: baseComponentBuilder{
cli,
Expand All @@ -153,6 +158,7 @@ func (f *DefaultComponentBuilderFactory) NewComponentBuilder(cli DockerEngineCli
copyOnWriteSemantics,
opts.LogWriter,
},
versioning: cnbVersioning,
}, nil
}

Expand Down
57 changes: 52 additions & 5 deletions internal/apps/builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,24 @@ func TestNewBuilderComponent(t *testing.T) {
require.ErrorContains(t, err, fmt.Sprintf("component %s not found", missingComponent))
})

t.Run("cnb builder", func(t *testing.T) {
t.Run("dockerfile builder", func(t *testing.T) {
builder, err := builderFactory.NewComponentBuilder(nil, ".", &godo.AppSpec{
Services: []*godo.AppServiceSpec{{
Name: "web",
DockerfilePath: ".",
}},
}, NewBuilderOpts{
Component: "web",
})
require.NoError(t, err)
require.IsTypef(t, &DockerComponentBuilder{}, builder, "expected DockerComponentBuilder but was %T", builder)
})
}

func TestNewBuilderComponent_CNB(t *testing.T) {
builderFactory := DefaultComponentBuilderFactory{}

t.Run("happy path", func(t *testing.T) {
builder, err := builderFactory.NewComponentBuilder(nil, ".", &godo.AppSpec{
Services: []*godo.AppServiceSpec{{
Name: "web",
Expand All @@ -44,18 +61,48 @@ func TestNewBuilderComponent(t *testing.T) {
})
require.NoError(t, err)
require.IsTypef(t, &CNBComponentBuilder{}, builder, "expected CNBComponentBuilder but was %T", builder)

cnbBuilder := builder.(*CNBComponentBuilder)
// no buildpacks in builder opts
require.Equal(t, CNBVersioning{}, cnbBuilder.versioning)
})

t.Run("dockerfile builder", func(t *testing.T) {
t.Run("buildpack versioning", func(t *testing.T) {
builder, err := builderFactory.NewComponentBuilder(nil, ".", &godo.AppSpec{
Services: []*godo.AppServiceSpec{{
Name: "web",
DockerfilePath: ".",
Name: "web",
}},
}, NewBuilderOpts{
Component: "web",
Versioning: Versioning{
CNB: &godo.AppBuildConfigCNBVersioning{
Buildpacks: []*godo.Buildpack{
{
ID: "digitalocean/node",
MajorVersion: 1,
},
{
ID: "digitalocean/go",
MajorVersion: 2,
},
},
},
},
})
require.NoError(t, err)
require.IsTypef(t, &DockerComponentBuilder{}, builder, "expected DockerComponentBuilder but was %T", builder)
cnbBuilder, ok := builder.(*CNBComponentBuilder)
require.True(t, ok, "expected CNBComponentBuilder but was %T", builder)
require.Equal(t, CNBVersioning{
Buildpacks: []*Buildpack{
{
ID: "digitalocean/node",
Version: "1.0.0",
},
{
ID: "digitalocean/go",
Version: "2.0.0",
},
},
}, cnbBuilder.versioning)
})
}
Loading

0 comments on commit c485a29

Please sign in to comment.