-
Notifications
You must be signed in to change notification settings - Fork 138
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: cron task for paketo's jammy builder (#1895)
* chore: cron task for BP jammy builder build Signed-off-by: Matej Vasek <[email protected]> * fixup: linter error Signed-off-by: Matej Vasek <[email protected]> * fixup: added nil checks Signed-off-by: Matej Vasek <[email protected]> --------- Signed-off-by: Matej Vasek <[email protected]>
- Loading branch information
1 parent
2463202
commit 6eac0d0
Showing
2 changed files
with
318 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
name: Update builder-jammy-full image | ||
|
||
on: | ||
schedule: | ||
- cron: '0 */4 * * *' | ||
|
||
jobs: | ||
build-and-push-image: | ||
permissions: | ||
contents: read | ||
packages: write | ||
runs-on: "ubuntu-latest" | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-go@v3 | ||
with: | ||
go-version: "1.20.x" | ||
- name: Build and Push | ||
env: | ||
GITHUB_TOKEN: ${{ github.token }} | ||
run: go run ./hack/update-builder.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,297 @@ | ||
package main | ||
|
||
import ( | ||
"archive/tar" | ||
"compress/gzip" | ||
"context" | ||
"encoding/base64" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"os/signal" | ||
"path/filepath" | ||
"strings" | ||
"syscall" | ||
|
||
"golang.org/x/oauth2" | ||
"golang.org/x/term" | ||
|
||
"github.com/buildpacks/pack/builder" | ||
pack "github.com/buildpacks/pack/pkg/client" | ||
"github.com/buildpacks/pack/pkg/dist" | ||
"github.com/buildpacks/pack/pkg/image" | ||
"github.com/docker/docker/api/types" | ||
"github.com/docker/docker/api/types/registry" | ||
docker "github.com/docker/docker/client" | ||
"github.com/docker/docker/pkg/jsonmessage" | ||
"github.com/google/go-containerregistry/pkg/authn" | ||
"github.com/google/go-containerregistry/pkg/name" | ||
"github.com/google/go-containerregistry/pkg/v1/remote" | ||
"github.com/google/go-github/v49/github" | ||
) | ||
|
||
func main() { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
sigs := make(chan os.Signal, 1) | ||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) | ||
go func() { | ||
<-sigs | ||
cancel() | ||
<-sigs | ||
os.Exit(130) | ||
}() | ||
|
||
err := buildBuilderImage(ctx) | ||
if err != nil { | ||
_, _ = fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func buildBuilderImage(ctx context.Context) error { | ||
buildDir, err := os.MkdirTemp("", "") | ||
if err != nil { | ||
return fmt.Errorf("cannot create temporary build directory: %w", err) | ||
} | ||
defer func(path string) { | ||
_ = os.RemoveAll(path) | ||
}(buildDir) | ||
|
||
ghClient := github.NewClient(oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{ | ||
AccessToken: os.Getenv("GITHUB_TOKEN"), | ||
}))) | ||
listOpts := &github.ListOptions{Page: 0, PerPage: 1} | ||
releases, ghResp, err := ghClient.Repositories.ListReleases(ctx, "paketo-buildpacks", "builder-jammy-full", listOpts) | ||
if err != nil { | ||
return fmt.Errorf("cannot get upstream builder release: %w", err) | ||
} | ||
defer func(Body io.ReadCloser) { | ||
_ = Body.Close() | ||
}(ghResp.Body) | ||
|
||
if len(releases) <= 0 { | ||
return fmt.Errorf("cannot get latest release") | ||
} | ||
|
||
release := releases[0] | ||
|
||
if release.Name == nil { | ||
return fmt.Errorf("the name of the release is not defined") | ||
} | ||
if release.TarballURL == nil { | ||
return fmt.Errorf("the tarball url of the release is not defined") | ||
} | ||
|
||
newBuilderImage := "ghcr.io/knative/builder-jammy-full" | ||
newBuilderImageTagged := newBuilderImage + ":" + *release.Name | ||
newBuilderImageLatest := newBuilderImage + ":latest" | ||
dockerUser := "gh-action" | ||
dockerPassword := os.Getenv("GITHUB_TOKEN") | ||
|
||
ref, err := name.ParseReference(newBuilderImageTagged) | ||
if err != nil { | ||
return fmt.Errorf("cannot parse reference to builder target: %w", err) | ||
} | ||
_, err = remote.Head(ref, remote.WithAuth(auth{dockerUser, dockerPassword})) | ||
if err == nil { | ||
fmt.Fprintln(os.Stderr, "The image has been already built.") | ||
return nil | ||
} | ||
|
||
builderTomlPath := filepath.Join(buildDir, "builder.toml") | ||
err = downloadBuilderToml(ctx, *release.TarballURL, builderTomlPath) | ||
if err != nil { | ||
return fmt.Errorf("cannot download builder toml: %w", err) | ||
} | ||
|
||
builderConfig, _, err := builder.ReadConfig(builderTomlPath) | ||
if err != nil { | ||
return fmt.Errorf("cannot parse builder.toml: %w", err) | ||
} | ||
|
||
patchBuilder(&builderConfig) | ||
|
||
packClient, err := pack.NewClient() | ||
if err != nil { | ||
return fmt.Errorf("cannot create pack client: %w", err) | ||
} | ||
createBuilderOpts := pack.CreateBuilderOptions{ | ||
RelativeBaseDir: buildDir, | ||
BuilderName: newBuilderImageTagged, | ||
Config: builderConfig, | ||
Publish: false, | ||
PullPolicy: image.PullIfNotPresent, | ||
} | ||
err = packClient.CreateBuilder(ctx, createBuilderOpts) | ||
if err != nil { | ||
return fmt.Errorf("canont create builder: %w", err) | ||
} | ||
|
||
dockerClient, err := docker.NewClientWithOpts(docker.FromEnv, docker.WithAPIVersionNegotiation()) | ||
if err != nil { | ||
return fmt.Errorf("cannot create docker client") | ||
} | ||
|
||
err = dockerClient.ImageTag(ctx, newBuilderImageTagged, newBuilderImageLatest) | ||
if err != nil { | ||
return fmt.Errorf("cannot tag latest image: %w", err) | ||
} | ||
|
||
authConfig := registry.AuthConfig{ | ||
Username: dockerUser, | ||
Password: dockerPassword, | ||
} | ||
bs, err := json.Marshal(&authConfig) | ||
if err != nil { | ||
return fmt.Errorf("cannot marshal credentials: %w", err) | ||
} | ||
imagePushOptions := types.ImagePushOptions{ | ||
All: false, | ||
RegistryAuth: base64.StdEncoding.EncodeToString(bs), | ||
} | ||
|
||
pushImage := func(image string) error { | ||
rc, err := dockerClient.ImagePush(ctx, image, imagePushOptions) | ||
if err != nil { | ||
return fmt.Errorf("cannot initialize image push: %w", err) | ||
} | ||
defer func(rc io.ReadCloser) { | ||
_ = rc.Close() | ||
}(rc) | ||
fd := os.Stdout.Fd() | ||
isTerminal := term.IsTerminal(int(os.Stdout.Fd())) | ||
err = jsonmessage.DisplayJSONMessagesStream(rc, os.Stderr, fd, isTerminal, nil) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
err = pushImage(newBuilderImageTagged) | ||
if err != nil { | ||
return fmt.Errorf("cannot push the image: %w", err) | ||
} | ||
|
||
err = pushImage(newBuilderImageLatest) | ||
if err != nil { | ||
return fmt.Errorf("cannot push the image: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type auth struct { | ||
uname, pwd string | ||
} | ||
|
||
func (a auth) Authorization() (*authn.AuthConfig, error) { | ||
return &authn.AuthConfig{ | ||
Username: a.uname, | ||
Password: a.pwd, | ||
}, nil | ||
} | ||
|
||
func downloadBuilderToml(ctx context.Context, tarballUrl, builderTomlPath string) error { | ||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, tarballUrl, nil) | ||
if err != nil { | ||
return fmt.Errorf("cannot create request for release tarball: %w", err) | ||
} | ||
//nolint:bodyclose | ||
resp, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return fmt.Errorf("cannot get release tarball: %w", err) | ||
} | ||
defer func(Body io.ReadCloser) { | ||
_ = Body.Close() | ||
}(resp.Body) | ||
|
||
gr, err := gzip.NewReader(resp.Body) | ||
if err != nil { | ||
return fmt.Errorf("cannot create gzip stream from release tarball: %w", err) | ||
} | ||
defer func(gr *gzip.Reader) { | ||
_ = gr.Close() | ||
}(gr) | ||
tr := tar.NewReader(gr) | ||
for { | ||
hdr, err := tr.Next() | ||
if err != nil { | ||
if errors.Is(err, io.EOF) { | ||
break | ||
} | ||
return fmt.Errorf("error while processing release tarball: %w", err) | ||
} | ||
|
||
if hdr.FileInfo().Mode().Type() != 0 || !strings.HasSuffix(hdr.Name, "/builder.toml") { | ||
continue | ||
} | ||
builderToml, err := os.OpenFile(builderTomlPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) | ||
if err != nil { | ||
return fmt.Errorf("cannot create builder.toml file: %w", err) | ||
} | ||
_, err = io.CopyN(builderToml, tr, hdr.Size) | ||
if err != nil { | ||
return fmt.Errorf("cannot copy data to builder.toml file: %w", err) | ||
} | ||
break | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Adds custom Rust and Go-Function buildpacks to the builder. | ||
func patchBuilder(config *builder.Config) { | ||
additionalBuildpacks := []builder.ModuleConfig{ | ||
{ | ||
ModuleInfo: dist.ModuleInfo{ | ||
ID: "paketo-community/rust", | ||
Version: "0.35.0", | ||
}, | ||
ImageOrURI: dist.ImageOrURI{ | ||
BuildpackURI: dist.BuildpackURI{URI: "docker://docker.io/paketocommunity/rust:0.35.0"}, | ||
}, | ||
}, | ||
{ | ||
ModuleInfo: dist.ModuleInfo{ | ||
ID: "dev.knative-sandbox.go", | ||
Version: "0.0.4", | ||
}, | ||
ImageOrURI: dist.ImageOrURI{ | ||
BuildpackURI: dist.BuildpackURI{URI: "ghcr.io/boson-project/go-function-buildpack:0.0.4"}, | ||
}, | ||
}, | ||
} | ||
|
||
additionalGroups := []dist.OrderEntry{ | ||
{ | ||
Group: []dist.ModuleRef{ | ||
{ | ||
ModuleInfo: dist.ModuleInfo{ | ||
ID: "paketo-community/rust", | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
Group: []dist.ModuleRef{ | ||
{ | ||
ModuleInfo: dist.ModuleInfo{ | ||
ID: "paketo-buildpacks/go-dist", | ||
}, | ||
}, | ||
{ | ||
ModuleInfo: dist.ModuleInfo{ | ||
ID: "dev.knative-sandbox.go", | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
config.Buildpacks = append(additionalBuildpacks, config.Buildpacks...) | ||
config.Order = append(additionalGroups, config.Order...) | ||
} |