-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
azure container registry runner #1107
Changes from 37 commits
21079db
ac5d24c
e7f1398
a5a5eac
cac520a
7e08ed3
14d691e
13e1155
44f869e
d14ee9f
15c6b3c
118938f
0662bc4
23e517e
b289bd1
f815042
a625bed
adf3bcc
e9b090a
1e49214
ffe32f3
8b60578
8528f48
5177b7f
783d7fb
f60d6ab
9284d4c
93a3e30
f49086a
88f8e32
45dd7ef
cd32955
f952830
efc504d
fce9621
622bbdf
12c32c7
9a5f2a4
d5dcbf5
2bf4e10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
FROM golang:alpine | ||
|
||
WORKDIR /go/src/github.com/GoogleCloudPlatform/skaffold | ||
CMD ["./app"] | ||
COPY main.go . | ||
RUN go build -o app main.go |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
=== Example: Azure Container Registry | ||
:icons: font | ||
|
||
This is an example demonstrating | ||
|
||
* *building* a single go file app and with a single stage `Dockerfile` using https://docs.microsoft.com/en-us/azure/container-registry/container-registry-tutorial-quick-task[ACR build] | ||
* *tagging* using the default tagPolicy (`gitCommit`) | ||
* *deploying* a single container pod using `kubectl` | ||
|
||
ifndef::env-github[] | ||
==== Example files | ||
link:{github-repo-tree}/examples/acr[see on Github icon:github[]] | ||
|
||
[source,yaml, indent=3, title=skaffold.yaml] | ||
---- | ||
include::skaffold.yaml[] | ||
---- | ||
|
||
[source,go, indent=3, title=main.go, syntax=go] | ||
---- | ||
include::main.go[] | ||
---- | ||
|
||
[source,docker, indent=3, title=Dockerfile] | ||
---- | ||
include::Dockerfile[] | ||
---- | ||
|
||
[source,yaml, indent=3, title=k8s-pod.yaml] | ||
---- | ||
include::k8s-pod.yaml[] | ||
---- | ||
|
||
endif::[] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
apiVersion: v1 | ||
kind: Pod | ||
metadata: | ||
name: getting-started-acr | ||
spec: | ||
containers: | ||
- name: getting-started | ||
image: registry.azurecr.io/skaffold-example |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
) | ||
|
||
func main() { | ||
for { | ||
fmt.Println("Hello world!") | ||
time.Sleep(time.Second * 1) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
apiVersion: skaffold/v1alpha4 | ||
kind: Config | ||
build: | ||
artifacts: | ||
- image: myregistry.azurecr.io/skaffold-example | ||
acr: {} | ||
deploy: | ||
kubectl: | ||
manifests: | ||
- k8s-* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
/* | ||
Copyright 2018 The Skaffold Authors | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package acr | ||
|
||
import ( | ||
"bufio" | ||
"context" | ||
"io" | ||
"net/http" | ||
"strings" | ||
"time" | ||
|
||
cr "github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2018-09-01/containerregistry" | ||
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" | ||
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag" | ||
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" | ||
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" | ||
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
const BuildStatusHeader = "x-ms-meta-Complete" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think you can leave this unexported |
||
|
||
func (b *Builder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*latest.Artifact) ([]build.Artifact, error) { | ||
return build.InParallel(ctx, out, tagger, artifacts, b.buildArtifact) | ||
} | ||
|
||
func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, tagger tag.Tagger, artifact *latest.Artifact) (string, error) { | ||
client, err := b.NewRegistriesClient() | ||
if err != nil { | ||
return "", errors.Wrap(err, "get new registries client") | ||
} | ||
|
||
imageTag, err := tagger.GenerateFullyQualifiedImageName(artifact.Workspace, &tag.Options{ | ||
Digest: util.RandomID(), | ||
ImageName: artifact.ImageName, | ||
}) | ||
if err != nil { | ||
return "", errors.Wrap(err, "create fully qualified image name") | ||
} | ||
registryName := getRegistryName(imageTag) | ||
|
||
resourceGroup, err := getResourceGroup(ctx, client, registryName) | ||
if err != nil { | ||
return "", errors.Wrap(err, "get resource group") | ||
} | ||
|
||
result, err := client.GetBuildSourceUploadURL(ctx, resourceGroup, registryName) | ||
if err != nil { | ||
return "", errors.Wrap(err, "build source upload url") | ||
} | ||
blob := NewBlobStorage(*result.UploadURL) | ||
|
||
err = docker.CreateDockerTarGzContext(ctx, blob.Buffer, artifact.Workspace, artifact.DockerArtifact) | ||
if err != nil { | ||
return "", errors.Wrap(err, "create context tar.gz") | ||
} | ||
|
||
err = blob.UploadFileToBlob() | ||
if err != nil { | ||
return "", errors.Wrap(err, "upload file to blob") | ||
} | ||
|
||
//acr needs the image tag formatted as <repository>:<tag> | ||
imageTag = getImageTagWithoutFQDN(imageTag) | ||
|
||
buildRequest := cr.DockerBuildRequest{ | ||
ImageNames: &[]string{imageTag}, | ||
IsPushEnabled: util.BoolPtr(true), | ||
SourceLocation: result.RelativePath, | ||
Platform: &cr.PlatformProperties{ | ||
Variant: cr.V8, | ||
Os: cr.Linux, | ||
Architecture: cr.Amd64, | ||
}, | ||
DockerFilePath: &artifact.DockerArtifact.DockerfilePath, | ||
Type: cr.TypeDockerBuildRequest, | ||
} | ||
future, err := client.ScheduleRun(ctx, resourceGroup, registryName, buildRequest) | ||
if err != nil { | ||
return "", errors.Wrap(err, "schedule build request") | ||
} | ||
|
||
run, err := future.Result(*client) | ||
if err != nil { | ||
return "", errors.Wrap(err, "get run id") | ||
} | ||
runID := *run.RunID | ||
|
||
runsClient := cr.NewRunsClient(b.SubscriptionID) | ||
runsClient.Authorizer = client.Authorizer | ||
logURL, err := runsClient.GetLogSasURL(ctx, resourceGroup, registryName, runID) | ||
if err != nil { | ||
return "", errors.Wrap(err, "get log url") | ||
} | ||
|
||
err = streamBuildLogs(*logURL.LogLink, out) | ||
if err != nil { | ||
return "", errors.Wrap(err, "polling build status") | ||
} | ||
|
||
return imageTag, nil | ||
} | ||
|
||
func streamBuildLogs(logURL string, out io.Writer) error { | ||
offset := int32(0) | ||
for { | ||
resp, err := http.Get(logURL) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if resp.StatusCode == http.StatusNotFound { | ||
//if blob is not available yet, try again | ||
time.Sleep(2 * time.Second) | ||
continue | ||
} | ||
|
||
scanner := bufio.NewScanner(resp.Body) | ||
line := int32(0) | ||
for scanner.Scan() { | ||
if line >= offset { | ||
out.Write(scanner.Bytes()) | ||
out.Write([]byte("\n")) | ||
offset++ | ||
} | ||
line++ | ||
} | ||
resp.Body.Close() | ||
|
||
if offset > 0 { | ||
switch resp.Header.Get(BuildStatusHeader) { | ||
case "": | ||
continue | ||
case "internalerror": | ||
case "failed": | ||
return errors.New("run failed") | ||
case "timedout": | ||
return errors.New("run timed out") | ||
case "canceled": | ||
return errors.New("run was canceled") | ||
default: | ||
return nil | ||
} | ||
} | ||
|
||
time.Sleep(2 * time.Second) | ||
} | ||
} | ||
|
||
func getResourceGroup(ctx context.Context, client *cr.RegistriesClient, registryName string) (string, error) { | ||
registryList, err := client.List(ctx) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
for _, registry := range registryList.Values() { | ||
if strings.ToLower(*registry.Name) == registryName { | ||
//registry.ID returns the exact path to the container registry | ||
//e.g. /subscriptions/<subscriptionId>/resourceGroups/<resourceGroup>/... | ||
//so the resourceGroup is the fourth element of the split | ||
return strings.Split(*registry.ID, "/")[4], nil | ||
} | ||
} | ||
|
||
return "", errors.New("Couldn't find resource group of registry") | ||
} | ||
|
||
func getImageTagWithoutFQDN(imageTag string) string { | ||
return imageTag[strings.Index(imageTag, "/")+1:] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think theres a better way to do this using the go-containerregistry. Can you look at an example of the other builders There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a test or two? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the tip with go-containerregistry 👍 |
||
} | ||
|
||
//acr URL is <registryname>.azurecr.io | ||
func getRegistryName(imageTag string) string { | ||
return strings.ToLower(imageTag[:strings.Index(imageTag, ".")]) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think integration/examples/annotated-skaffold.yaml should be modified instead (or in addition to this one), so that when we copy over config changes we don't lose this.