-
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 27 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 gcr.io/google-appengine/golang | ||
|
||
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 | ||
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. We are in a nasty chicken and egg situation were a sample config can't be put in You should move the whole sample to |
||
: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,17 @@ | ||
apiVersion: skaffold/v1alpha3 | ||
kind: Config | ||
build: | ||
artifacts: | ||
- imageName: myregistry.azurecr.io/skaffold-example | ||
acr: | ||
containerRegistry: myregistry | ||
resourceGroup: myresourcegroup | ||
credentials: | ||
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. Can the credentials be found automatically by Skaffold like it's done for GCB? With GCB, you don't have to configure anything in Skaffold if I thing both scenario make sense :
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 could create an Authorizer which uses the bearer token from the azure cli config. So the priority would be: try to use provided credentials, if they're not present try to authenticate with bearer from the config, if the bearer has expired throw an error. I could try to call the azure cli to refresh the bearer though... 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. So I managed to retrieve all required information from the Azure CLI and other methods, which makes the config basically as empty as:
You can still provide the credentials, but you don't have to specify the resourceGroup and registryName explicitly. |
||
subscriptionId: subscription-id | ||
clientId: client-id | ||
clientSecret: client-secret | ||
tenantId: tenant-id | ||
deploy: | ||
kubectl: | ||
manifests: | ||
- k8s-* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
/* | ||
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" | ||
"regexp" | ||
"time" | ||
|
||
cr "github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2018-09-01/containerregistry" | ||
"github.com/Azure/go-autorest/autorest/azure/auth" | ||
"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 := cr.NewRegistriesClient(b.Credentials.SubscriptionID) | ||
authorizer, err := auth.NewClientCredentialsConfig(b.Credentials.ClientID, b.Credentials.ClientSecret, b.Credentials.TenantID).Authorizer() | ||
if err != nil { | ||
return "", errors.Wrap(err, "authorizing client") | ||
} | ||
client.Authorizer = authorizer | ||
|
||
result, err := client.GetBuildSourceUploadURL(ctx, b.ResourceGroup, b.ContainerRegistry) | ||
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. Does that give <> urls for two artifacts that are built in parallel? 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. Unfortunately not. When uploading to this URL, it saves the tar to a specific location And it only returns a single URL. |
||
if err != nil { | ||
return "", errors.Wrap(err, "build source upload url") | ||
} | ||
blob := NewBlobStorage(*result.UploadURL) | ||
|
||
err = docker.CreateDockerTarGzContext(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") | ||
} | ||
|
||
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") | ||
} | ||
|
||
imageTag, err = getImageTagWithoutFQDN(imageTag) | ||
if err != nil { | ||
return "", errors.Wrap(err, "get azure image tag") | ||
} | ||
|
||
buildRequest := cr.DockerBuildRequest{ | ||
ImageNames: &[]string{imageTag}, | ||
IsPushEnabled: &[]bool{true}[0], //who invented bool pointers | ||
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. Nice trick! You can use |
||
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, b.ResourceGroup, b.ContainerRegistry, 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.Credentials.SubscriptionID) | ||
runsClient.Authorizer = client.Authorizer | ||
logURL, err := runsClient.GetLogSasURL(ctx, b.ResourceGroup, b.ContainerRegistry, 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) | ||
} | ||
} | ||
|
||
// ACR needs the image tag in the following format | ||
// <repository>:<tag> | ||
func getImageTagWithoutFQDN(imageTag string) (string, error) { | ||
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. Could you add a few test for this piece of code? Could you replace the manual parsing a an image name with something like 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.
|
||
r, err := regexp.Compile(`.*\..*\..*/(.*)`) | ||
if err != nil { | ||
return "", errors.Wrap(err, "create regexp") | ||
} | ||
|
||
matches := r.FindStringSubmatch(imageTag) | ||
if len(matches) < 2 { | ||
return "", errors.New("invalid image tag") | ||
} | ||
|
||
return matches[1], nil | ||
} |
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.
nit, you might want to use a smaller image to accelerate the build. (on GCR, we use
gcr.io/*
images because some of them are pre-pulled on the build node.)