Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows Support #546

Merged
merged 2 commits into from
Jan 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/openapi-spec/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -4604,6 +4604,9 @@
"$ref": "#/definitions/kpack.build.v1alpha1.OrderEntry"
}
},
"os": {
"type": "string"
},
"stack": {
"default": {},
"$ref": "#/definitions/kpack.build.v1alpha1.BuildStack"
Expand Down
89 changes: 82 additions & 7 deletions cmd/build-init/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package main

import (
"flag"
"io"
"log"
"net"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"time"

"github.com/google/go-containerregistry/pkg/authn"
"github.com/pkg/errors"
Expand All @@ -29,6 +33,7 @@ var (
gitRevision = flag.String("git-revision", os.Getenv("GIT_REVISION"), "The Git revision to make the repository HEAD.")
blobURL = flag.String("blob-url", os.Getenv("BLOB_URL"), "The url of the source code blob.")
registryImage = flag.String("registry-image", os.Getenv("REGISTRY_IMAGE"), "The registry location of the source code image.")
hostName = flag.String("dns-probe-hostname", os.Getenv("DNS_PROBE_HOSTNAME"), "hostname to dns poll")

buildChanges = flag.String("build-changes", os.Getenv("BUILD_CHANGES"), "JSON string of build changes and their reason")

Expand All @@ -48,20 +53,27 @@ func init() {
}

const (
secretsHome = "/builder/home"
appDir = "/workspace"
platformDir = "/platform"
buildSecretsDir = "/var/build-secrets"
imagePullSecretsDir = "/imagePullSecrets"
builderPullSecretsDir = "/builderPullSecrets"
projectMetadataDir = "/projectMetadata"
secretsHome = "/builder/home"
appDir = "/workspace"
platformDir = "/platform"
buildSecretsDir = "/var/build-secrets"
imagePullSecretsDir = "/imagePullSecrets"
builderPullSecretsDir = "/builderPullSecrets"
projectMetadataDir = "/projectMetadata"
networkWaitLauncherDir = "/networkWait"
networkWaitLauncherBinary = "network-wait-launcher.exe"
)

func main() {
flag.Parse()

logger := log.New(os.Stdout, "", 0)

err := prepareForWindows(*hostName)
if err != nil {
logger.Fatal(err)
}

if err := buildchange.Log(logger, *buildChanges); err != nil {
logger.Println(err)
}
Expand Down Expand Up @@ -126,6 +138,26 @@ func main() {
}
}

func prepareForWindows(hostname string) error {
if runtime.GOOS != "windows" {
return nil
}

executablePath, err := os.Executable()
if err != nil {
return err
}

err = copyFile(filepath.Join(filepath.Dir(executablePath), networkWaitLauncherBinary), filepath.Join(networkWaitLauncherDir, networkWaitLauncherBinary))
if err != nil {
return err
}

waitForDns(hostname)

return nil
}

func fetchSource(logger *log.Logger, serviceAccountCreds dockercreds.DockerCreds) error {
switch {
case *gitURL != "":
Expand Down Expand Up @@ -175,3 +207,46 @@ func logLoadingSecrets(logger *log.Logger, secretsSlices ...[]string) {
}
}
}

func waitForDns(hostname string) {
timeoutChan := time.After(10 * time.Second)
tickerChan := time.NewTicker(time.Second)
defer tickerChan.Stop()

for {
select {
case <-timeoutChan:
return
case <-tickerChan.C:
if _, err := net.LookupIP(hostname); err == nil {
return
}
}
}
}

func copyFile(src, dest string) error {
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()

destFile, err := os.Create(dest)
if err != nil {
return err
}
defer destFile.Close()

if _, err = io.Copy(destFile, srcFile); err != nil {
return err
}

srcInfo, err := os.Stat(src)
if err != nil {
return err
}

return os.Chmod(dest, srcInfo.Mode())
}

18 changes: 11 additions & 7 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@ var (
kubeconfig = flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
masterURL = flag.String("master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")

buildInitImage = flag.String("build-init-image", os.Getenv("BUILD_INIT_IMAGE"), "The image used to initialize a build")
rebaseImage = flag.String("rebase-image", os.Getenv("REBASE_IMAGE"), "The image used to perform rebases")
completionImage = flag.String("completion-image", os.Getenv("COMPLETION_IMAGE"), "The image used to finish a build")
lifecycleImage = flag.String("lifecycle-image", os.Getenv("LIFECYCLE_IMAGE"), "The image used to provide lifecycle binaries")
buildInitImage = flag.String("build-init-image", os.Getenv("BUILD_INIT_IMAGE"), "The image used to initialize a build")
buildInitWindowsImage = flag.String("build-init-windows-image", os.Getenv("BUILD_INIT_WINDOWS_IMAGE"), "The image used to initialize a build on windows")
rebaseImage = flag.String("rebase-image", os.Getenv("REBASE_IMAGE"), "The image used to perform rebases")
completionImage = flag.String("completion-image", os.Getenv("COMPLETION_IMAGE"), "The image used to finish a build")
completionWindowsImage = flag.String("completion-windows-image", os.Getenv("COMPLETION_WINDOWS_IMAGE"), "The image used to finish a build on windows")
lifecycleImage = flag.String("lifecycle-image", os.Getenv("LIFECYCLE_IMAGE"), "The image used to provide lifecycle binaries")
)

func main() {
Expand Down Expand Up @@ -123,9 +125,11 @@ func main() {

buildpodGenerator := &buildpod.Generator{
BuildPodConfig: v1alpha1.BuildPodImages{
BuildInitImage: *buildInitImage,
CompletionImage: *completionImage,
RebaseImage: *rebaseImage,
BuildInitImage: *buildInitImage,
CompletionImage: *completionImage,
RebaseImage: *rebaseImage,
BuildInitWindowsImage: *buildInitWindowsImage,
CompletionWindowsImage: *completionWindowsImage,
},
K8sClient: k8sClient,
KeychainFactory: keychainFactory,
Expand Down
49 changes: 49 additions & 0 deletions cmd/network-wait-launcher/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"errors"
"log"
"net"
"os"
"os/exec"
"time"
)

func main() {
// example: /kpack/network-wait-launcher gcr.io -- completion --notary-url="some-url"

hostname := os.Args[1]
command := os.Args[3:]

if err := waitForDns(hostname); err != nil {
log.Fatal(err)
}

cmd := exec.Command(command[0], command[1:]...)
tylerphelan marked this conversation as resolved.
Show resolved Hide resolved

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}

}

func waitForDns(hostname string) error {
timeoutChan := time.After(10 * time.Second)
tickerChan := time.NewTicker(time.Second)
defer tickerChan.Stop()

for {
select {
case <-timeoutChan:
return errors.New("timeout waiting for network")
case <-tickerChan.C:
_, err := net.LookupIP(hostname)
if err == nil {
return nil
}
}
}

}
26 changes: 26 additions & 0 deletions config/controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ data:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: build-init-windows-image
namespace: kpack
data:
image: #@ data.values.build_init_windows_image
---
apiVersion: v1
kind: ConfigMap
metadata:
name: rebase-image
namespace: kpack
Expand All @@ -33,6 +41,14 @@ metadata:
data:
image: #@ data.values.completion_image
---
apiVersion: v1
kind: ConfigMap
metadata:
name: completion-windows-image
namespace: kpack
data:
image: #@ data.values.completion_windows_image
---
apiVersion: apps/v1
kind: Deployment
metadata:
Expand Down Expand Up @@ -71,6 +87,11 @@ spec:
configMapKeyRef:
name: build-init-image
key: image
- name: BUILD_INIT_WINDOWS_IMAGE
valueFrom:
configMapKeyRef:
name: build-init-windows-image
key: image
- name: REBASE_IMAGE
valueFrom:
configMapKeyRef:
Expand All @@ -81,6 +102,11 @@ spec:
configMapKeyRef:
name: completion-image
key: image
- name: COMPLETION_WINDOWS_IMAGE
valueFrom:
configMapKeyRef:
name: completion-windows-image
key: image
- name: LIFECYCLE_IMAGE
valueFrom:
configMapKeyRef:
Expand Down
2 changes: 2 additions & 0 deletions config/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
controller_image: gcr.io/controller
webhook_image: gcr.io/webhook
build_init_image: gcr.io/build-init
build_init_windows_image: gcr.io/cf-build-service-public/kpack/build-init-windows
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the expectation that this is built/updated out of band of ./hack/release.sh?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, at least for now. Maybe in the future we could conceivably build the images using something other than pack that could run on linux and still build the windows images

rebase_image: gcr.io/rebase
completion_image: gcr.io/completion
completion_windows_image: gcr.io/cf-build-service-public/kpack/completion-windows
lifecycle_image: gcr.io/lifecycle
version: dev
53 changes: 46 additions & 7 deletions hack/lifecycle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"regexp"
"time"

"github.com/buildpacks/imgutil/layer"
"github.com/google/go-containerregistry/pkg/authn"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
Expand All @@ -41,10 +42,11 @@ func main() {
flag.Parse()

image, err := lifecycleImage(
"https://github.com/buildpacks/lifecycle/releases/download/v0.9.2/lifecycle-v0.9.2+linux.x86-64.tgz",
"https://github.com/buildpacks/lifecycle/releases/download/v0.9.3/lifecycle-v0.9.3+linux.x86-64.tgz",
"https://github.com/buildpacks/lifecycle/releases/download/v0.9.3/lifecycle-v0.9.3+windows.x86-64.tgz",
cnb.LifecycleMetadata{
LifecycleInfo: cnb.LifecycleInfo{
Version: "0.9.2",
Version: "0.9.3",
},
API: cnb.LifecycleAPI{
BuildpackVersion: "0.2",
Expand Down Expand Up @@ -75,18 +77,42 @@ func main() {

}

func lifecycleImage(url string, lifecycleMetadata cnb.LifecycleMetadata) (v1.Image, error) {
func lifecycleImage(linuxUrl, windowsUrl string, lifecycleMetadata cnb.LifecycleMetadata) (v1.Image, error) {
image, err := random.Image(0, 0)
if err != nil {
return nil, err
}

layer, err := lifecycleLayer(url)
linuxLayer, err := lifecycleLayer(linuxUrl, "linux")
if err != nil {
return nil, err
}
linuxDiffID, err := linuxLayer.DiffID()
if err != nil {
return nil, err
}

image, err = imagehelpers.SetStringLabel(image, "linux", linuxDiffID.String())
if err != nil {
return nil, err
}

windowsLayer, err := lifecycleLayer(windowsUrl, "windows")
if err != nil {
return nil, err
}

windowsDiffID, err := windowsLayer.DiffID()
if err != nil {
return nil, err
}

image, err = imagehelpers.SetStringLabel(image, "windows", windowsDiffID.String())
if err != nil {
return nil, err
}

image, err = mutate.AppendLayers(image, layer)
image, err = mutate.AppendLayers(image, linuxLayer, windowsLayer)
if err != nil {
return nil, err
}
Expand All @@ -96,9 +122,9 @@ func lifecycleImage(url string, lifecycleMetadata cnb.LifecycleMetadata) (v1.Ima
})
}

func lifecycleLayer(url string) (v1.Layer, error) {
func lifecycleLayer(url, os string) (v1.Layer, error) {
b := &bytes.Buffer{}
tw := tar.NewWriter(b)
tw := newLayerWriter(b, os)

var regex = regexp.MustCompile(`^[^/]+/([^/]+)$`)

Expand Down Expand Up @@ -197,3 +223,16 @@ type ReadCloserWrapper struct {
func (r *ReadCloserWrapper) Close() error {
return r.closer()
}

func newLayerWriter(fileWriter io.Writer, os string) layerWriter {
if os == "windows" {
return layer.NewWindowsWriter(fileWriter)
}
return tar.NewWriter(fileWriter)
}

type layerWriter interface {
WriteHeader(hdr *tar.Header) error
Write(b []byte) (int, error)
Close() error
}
Loading