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

Add labels to all k8s objects deployed by skaffold #644

Merged
merged 1 commit into from
Jun 13, 2018

Conversation

nkubala
Copy link
Contributor

@nkubala nkubala commented Jun 6, 2018

This adds labels to all kubernetes objects deployed by Skaffold. By default, the label deployed_with:skaffold is added to all objects, as well as a skaffold_deploy_client:<client> with the deployer used in skaffold. We can add more labels or change these as we see fit.
Each Builder, Deployer, and Tagger implementation now exposes its own set of labels to the Runner, which will aggregate all labels and send those to the deployed objects on each run.

We should consider adding labels for individual profiles as well, possibly in a follow-up PR.

Also, as a small non-functional change, I changed build.Build to build.BuildArtifact to match deploy.DeployArtifact.

Fixes #98

}
labelDeployResults(deployResults)
Copy link
Contributor

Choose a reason for hiding this comment

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

To avoid having to run this in each deployer, I would suggest that we change the deploy interface back to

Deploy(ctx context.Context, out io.Writer, builds []build.Build) (DeployResult, error)

That maybe looks like

type DeployResult struct {
  deployedArtifacts []DeployedArtifact
}
  
type DeployedArtifact struct {
  GVK string
  obj *runtime.Obj
}

The high level flow would be

func run() {
 buildResults := builder.build()
 deployResults := deployer.deploy(buildResults)
 label(cfg, deployResults)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had played around with having this live in the runner, but decided to go with putting it in each deployer for two reasons:

  1. the runner will need to know less about what happened in its individually managed steps, and
  2. labeling should be coupled to deploying, since we can't label unless we've successfully deployed.

That said, I can see arguments for moving it to the runner as well. If we add more deployers that's more boilerplate we have to remember to add, and the runner has a lot more information about the overall skaffold run which could be useful labeling information. Since the runner has all the skaffold config info, I think that's a strong enough argument for moving it back, so I'll go ahead with it and see how it looks.

// DeployResult is a holder struct encapsulating the deployed k8s objects
type DeployResult struct {
obj *runtime.Object
namespace string
Copy link
Contributor

Choose a reason for hiding this comment

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

The namespace is part of of the runtime.Object I believe

Copy link
Contributor Author

Choose a reason for hiding this comment

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

unfortunately it isn't always, helm can't be trusted to put it where it should be, among many other things that it should do but doesn't

type DeployResult struct {
obj *runtime.Object
namespace string
deployer string
Copy link
Contributor

Choose a reason for hiding this comment

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

We can infer this from the config. I'm OK with adding a Name() string requirement to the deploy interface

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed as per my comment above

@r2d4 r2d4 self-assigned this Jun 7, 2018
return "default"
}

func updateRuntimeObject(client clientgo.Interface, labels map[string]string, res Artifact) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the dynamic client should make this easy.

https://github.com/kubernetes/client-go/blob/master/dynamic/interface.go

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like client-go itself is discouraging use of the dynamic client: kubernetes/client-go#394. Even though it looks a little messy I think sticking to the typed APIs is safer and probably a better idea

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 we should use it anyways. Lets keep it out of scope for this PR though.

@r2d4 r2d4 mentioned this pull request Jun 8, 2018
const tries int = 3
const sleeptime time.Duration = 300 * time.Millisecond

func LabelDeployResults(labels map[string]string, results []Artifact) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since labels come from the runner, this should be in the runner package

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved to runner/labels

return "default"
}

func updateRuntimeObject(client clientgo.Interface, labels map[string]string, res Artifact) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a TODO here to get rid of this in favor of the dynamic client or something else

@@ -32,6 +34,12 @@ import (
type GitCommit struct {
}

func (c *GitCommit) Labels() map[string]string {
return map[string]string{
constants.Labels.TagPolicy: "git_commit",
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: consistency gitCommit

func (opts *SkaffoldOptions) Labels() map[string]string {
labels := map[string]string{}
if opts.CustomTag != "" {
labels["custom_tag"] = "true"
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd remove this

Add

  • namespace
  • profiles
  • cleanup

Copy link
Contributor Author

Choose a reason for hiding this comment

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

profiles is a little special because this will just contain the list of all profiles specified on the CLI, but not which one was actually used to create each k8s API object. was planning on opening an issue to add support for profile labeling outside this PR

if err != nil {
return err
dRes, err := w.Deployer.Deploy(ctx, out, builds)
if err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

this error goes unchecked

dRes, err := w.Deployer.Deploy
if err != nil {
  return nil, err
}
fmt.Fprintln
return dRes, nil

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch, got the logic slightly wrong here. that's equivalent to

dRes, err := w.Deployer.Deploy
if err == nil {
  fmt.Fprintln
}
return dRes, err

which is what I was trying to write.

// Retrieve info about all releases using helm get
// Skaffold labels will be applied to each deployed k8s object
// Since helm isn't always consistent with retrieving results, don't return errors here
func (h *HelmDeployer) getDeployResults(namespace string, release string) []Artifact {
Copy link
Contributor

Choose a reason for hiding this comment

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

can you add a test for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// Artifact contains all information about a completed deployment
type Artifact struct {
obj *runtime.Object
namespace string
Copy link
Contributor

Choose a reason for hiding this comment

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

why we need namespace here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the namespace used to do a deployment doesn't always match the namespace in the deployer, and we need access to it when updating the API objects with the labels

DefaultLabels map[string]string
}{
DefaultLabels: map[string]string{
"deployed_with": "skaffold",
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -64,6 +64,12 @@ func NewLocalBuilder(cfg *v1alpha2.BuildConfig, kubeContext string) (*LocalBuild
return l, nil
}

func (l *LocalBuilder) Labels() map[string]string {
return map[string]string{
constants.Labels.Builder: "local",
Copy link
Contributor

Choose a reason for hiding this comment

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

nit:
add some context from the docker api

"docker-api-version": api.ServerVersion(ctx)

func (cb *GoogleCloudBuilder) Build(ctx context.Context, out io.Writer, tagger tag.Tagger, artifacts []*v1alpha2.Artifact) ([]Build, error) {
func (cb *GoogleCloudBuilder) Labels() map[string]string {
return map[string]string{
constants.Labels.Builder: "GoogleCloudBuilder",
Copy link
Contributor

Choose a reason for hiding this comment

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

google-cloud-builder

@nkubala nkubala force-pushed the labels branch 3 times, most recently from 93106ed to a52436d Compare June 12, 2018 22:56
@nkubala
Copy link
Contributor Author

nkubala commented Jun 12, 2018

labels


func (h *HelmDeployer) getReleaseInfo(release string) (*bufio.Reader, error) {
var releaseInfo bytes.Buffer
out := bufio.NewWriter(&releaseInfo)
Copy link
Contributor

Choose a reason for hiding this comment

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

You shouldn't need this bufio.NewWriter, you can use a var releaseInfo bytes.Buffer directly as a reader/writer:
https://golang.org/pkg/bytes/#Buffer.Write

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep, thanks

break
}
if err != nil {
logrus.Infof("error parsing helm deployment: %s", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

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

should we have a continue here to avoid trying to parse an invalid or nil doc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good call, added

func parseManifestsForDeploys(manifests manifestList) ([]Artifact, error) {
results := []Artifact{}
for _, manifest := range manifests {
b := bufio.NewReader(bytes.NewReader(manifest))
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you wrap this in bufio?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the YAMLReader from apimachinery requires a bufio.Reader instead of an io.Reader: https://github.com/kubernetes/apimachinery/blob/master/pkg/util/yaml/decoder.go#L262

@nkubala nkubala merged commit dade08e into GoogleContainerTools:master Jun 13, 2018
Copy link
Contributor

@balopat balopat left a comment

Choose a reason for hiding this comment

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

I see I was too slow. Added couple of nits maybe for next time.

@@ -50,7 +51,7 @@ func NewCmdDeploy(out io.Writer) *cobra.Command {
func runDeploy(out io.Writer, filename string) error {
ctx := context.Background()

runner, _, err := newRunner(filename)
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'd think runner is short enough.

@@ -40,6 +42,12 @@ func NewDateTimeTagger(format, timezone string) Tagger {
}
}

func (tagger *dateTimeTagger) Labels() map[string]string {
return map[string]string{
constants.Labels.TagPolicy: "dateTimeTagger",
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this could be just dateTime to be consistent

@@ -40,8 +41,14 @@ func NewEnvTemplateTagger(t string) (Tagger, error) {
}, nil
}

func (t *envTemplateTagger) Labels() map[string]string {
return map[string]string{
constants.Labels.TagPolicy: "envTemplateTagger",
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this could be just envTemplate to be consistent

const sleeptime time.Duration = 300 * time.Millisecond

//nolint
func LabelDeployResults(labels map[string]string, results []deploy.Artifact) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: how about ApplyLabels?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants