mirror of https://github.com/knative/func.git
				
				
				
			chore: cron task for paketo's jammy builder (#1895)
* chore: cron task for BP jammy builder build Signed-off-by: Matej Vasek <mvasek@redhat.com> * fixup: linter error Signed-off-by: Matej Vasek <mvasek@redhat.com> * fixup: added nil checks Signed-off-by: Matej Vasek <mvasek@redhat.com> --------- Signed-off-by: Matej Vasek <mvasek@redhat.com>
This commit is contained in:
		
							parent
							
								
									2463202f11
								
							
						
					
					
						commit
						6eac0d0605
					
				|  | @ -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 | ||||
|  | @ -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...) | ||||
| } | ||||
		Loading…
	
		Reference in New Issue