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

azure container registry runner #1107

Merged
merged 40 commits into from
Oct 15, 2018
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
21079db
Create new config version to support azure container registry builds
cedrickring Oct 3, 2018
ac5d24c
Added AzureContainerBuild to BuildType
cedrickring Oct 3, 2018
e7f1398
Added builder for azure container registry
cedrickring Oct 6, 2018
a5a5eac
Removed blank line
cedrickring Oct 6, 2018
cac520a
Use buffer pointer instead of buffer in azure blob storage
cedrickring Oct 7, 2018
7e08ed3
ensured all packages
cedrickring Oct 7, 2018
14d691e
Using proper paths inside source tar
cedrickring Oct 7, 2018
13e1155
Use sourceLocation from GetBuildSourceUploadURL
cedrickring Oct 7, 2018
44f869e
Formatting image tags to match azures requirements
cedrickring Oct 7, 2018
d14ee9f
Added build status polling
cedrickring Oct 7, 2018
15c6b3c
Try again if log blob is not available yet
cedrickring Oct 7, 2018
118938f
Fix missing output
cedrickring Oct 7, 2018
0662bc4
Check if response has data before checking for completion
cedrickring Oct 7, 2018
23e517e
always increment log lines
cedrickring Oct 7, 2018
b289bd1
Fix output
cedrickring Oct 7, 2018
f815042
Fix build context
cedrickring Oct 7, 2018
a625bed
Fixed image tag formatting, now using <repository>:<tag>
cedrickring Oct 7, 2018
adf3bcc
Fixed image tag regexp
cedrickring Oct 7, 2018
e9b090a
Renamed method to streamBuildLogs
cedrickring Oct 7, 2018
1e49214
Added example for Azure Container Registry builds
cedrickring Oct 7, 2018
ffe32f3
Fixed merge conflicts
cedrickring Oct 7, 2018
8b60578
Added boilerplate to new files
cedrickring Oct 7, 2018
8528f48
Changed variable names to conform golint
cedrickring Oct 7, 2018
5177b7f
Sorted imports by goimports
cedrickring Oct 7, 2018
783d7fb
Using raw string in regexp
cedrickring Oct 7, 2018
f60d6ab
Sorted imports with goimports
cedrickring Oct 7, 2018
9284d4c
Use version v1alpha4 for config changes
cedrickring Oct 8, 2018
93a3e30
Moved acr example to integration/examples
cedrickring Oct 8, 2018
f49086a
Using golang:alpine as baseimage for acr example
cedrickring Oct 8, 2018
88f8e32
Closing blob upload response now
cedrickring Oct 8, 2018
45dd7ef
Closing blob upload response after checking if an error has occurred
cedrickring Oct 8, 2018
cd32955
Simplified removing FQDN from image tag
cedrickring Oct 10, 2018
f952830
Derive registry name from image tag
cedrickring Oct 10, 2018
efc504d
Merge remote-tracking branch 'upstream/master'
cedrickring Oct 10, 2018
fce9621
Synced upstream
cedrickring Oct 10, 2018
622bbdf
Retrieve bearer token from azure cli if credentials are not provided …
cedrickring Oct 11, 2018
12c32c7
Forgot the error message
cedrickring Oct 11, 2018
9a5f2a4
Using integration/examples for examples instead of examples/
cedrickring Oct 12, 2018
d5dcbf5
Don't export build status header
cedrickring Oct 12, 2018
2bf4e10
Use go-containerregistry to get image tag for acr
cedrickring Oct 12, 2018
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
31 changes: 29 additions & 2 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions examples/annotated-skaffold.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ build:
# namespace: default
# timeout: 20m

# Docker artifacts can be built on an Azure Container Registry.
Copy link
Contributor

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.

# If Azure CLI is configured properly, you're logged in and have access to the registry,
# you can use:
#
# acr: {}
#
# otherwise you need to provide the credentials manually
# acr:
# subscriptionId: SUBSCRIPTION_ID
# tenantId: TENANT_ID
# clientId: CLIENT_ID
# clientSecret: CLIENT_SECRET

# The deploy section has all the information needed to deploy. Along with build:
# it is a required section.
deploy:
Expand Down
6 changes: 6 additions & 0 deletions integration/examples/acr/Dockerfile
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
34 changes: 34 additions & 0 deletions integration/examples/acr/README.adoc
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::[]
8 changes: 8 additions & 0 deletions integration/examples/acr/k8s-pod.yaml
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
13 changes: 13 additions & 0 deletions integration/examples/acr/main.go
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)
}
}
10 changes: 10 additions & 0 deletions integration/examples/acr/skaffold.yaml
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-*
190 changes: 190 additions & 0 deletions pkg/skaffold/build/acr/acr_build.go
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"
Copy link
Contributor

Choose a reason for hiding this comment

The 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:]
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe add a test or two?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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, ".")])
}
Loading