Skip to content
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
8 changes: 7 additions & 1 deletion infra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ usage: infra [<flags>] <command> [<args> ...]
The prometheus/test-infra deployment tool

Flags:
-h, --help Show context-sensitive help (also try --help-long and --help-man).
-h, --help Show context-sensitive help (also try --help-long and
--help-man).
-f, --file=FILE ... yaml file or folder that describes the parameters for the
object that will be deployed.
-v, --vars=VARS ... When provided it will substitute the token holders in the
Comment thread
krasi-georgiev marked this conversation as resolved.
yaml file. Follows the standard golang template formating
- {{ .hashStable }}.

Commands:
help [<command>...]
Expand Down
27 changes: 16 additions & 11 deletions infra/infra.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,36 @@ import (
"path/filepath"

"github.com/pkg/errors"
"github.com/prometheus/test-infra/pkg/provider"
"github.com/prometheus/test-infra/pkg/provider/gke"
"gopkg.in/alecthomas/kingpin.v2"
)

func main() {
log.SetFlags(log.Ltime | log.Lshortfile)

dr := provider.NewDeploymentResource()

app := kingpin.New(filepath.Base(os.Args[0]), "The prometheus/test-infra deployment tool")
app.HelpFlag.Short('h')
app.Flag("file", "yaml file or folder that describes the parameters for the object that will be deployed.").
Short('f').
ExistingFilesOrDirsVar(&dr.DeploymentFiles)
app.Flag("vars", "When provided it will substitute the token holders in the yaml file. Follows the standard golang template formating - {{ .hashStable }}.").
Short('v').
StringMapVar(&dr.FlagDeploymentVars)

g := gke.New()
g := gke.New(dr)
k8sGKE := app.Command("gke", `Google container engine provider - https://cloud.google.com/kubernetes-engine/`).
Action(g.NewGKEClient)
Action(g.SetupDeploymentResources)
k8sGKE.Flag("auth", "json authentication for the project. Accepts a filepath or an env variable that inlcudes tha json data. If not set the tool will use the GOOGLE_APPLICATION_CREDENTIALS env variable (export GOOGLE_APPLICATION_CREDENTIALS=service-account.json). https://cloud.google.com/iam/docs/creating-managing-service-account-keys.").
PlaceHolder("service-account.json").
Short('a').
StringVar(&g.Auth)
k8sGKE.Flag("file", "yaml file or folder that describes the parameters for the object that will be deployed.").
Required().
Short('f').
ExistingFilesOrDirsVar(&g.DeploymentFiles)
k8sGKE.Flag("vars", "When provided it will substitute the token holders in the yaml file. Follows the standard golang template formating - {{ .hashStable }}.").
Short('v').
StringMapVar(&g.DeploymentVars)

// Cluster operations.
k8sGKECluster := k8sGKE.Command("cluster", "manage GKE clusters").
Action(g.NewGKEClient).
Comment thread
geekodour marked this conversation as resolved.
Action(g.GKEDeploymentsParse)
k8sGKECluster.Command("create", "gke cluster create -a service-account.json -f FileOrFolder").
Action(g.ClusterCreate)
Expand All @@ -55,6 +58,7 @@ func main() {

// Cluster node-pool operations
k8sGKENodePool := k8sGKE.Command("nodepool", "manage GKE clusters nodepools").
Action(g.NewGKEClient).
Action(g.GKEDeploymentsParse)
k8sGKENodePool.Command("create", "gke nodepool create -a service-account.json -f FileOrFolder").
Action(g.NodePoolCreate)
Expand All @@ -67,8 +71,9 @@ func main() {

// K8s resource operations.
k8sGKEResource := k8sGKE.Command("resource", `Apply and delete different k8s resources - deployments, services, config maps etc.Required variables -v PROJECT_ID, -v ZONE: -west1-b -v CLUSTER_NAME`).
Action(g.NewK8sProvider).
Action(g.K8SDeploymentsParse)
Action(g.NewGKEClient).
Action(g.K8SDeploymentsParse).
Action(g.NewK8sProvider)
k8sGKEResource.Command("apply", "gke resource apply -a service-account.json -f manifestsFileOrFolder -v PROJECT_ID:test -v ZONE:europe-west1-b -v CLUSTER_NAME:test -v hashStable:COMMIT1 -v hashTesting:COMMIT2").
Action(g.ResourceApply)
k8sGKEResource.Command("delete", "gke resource delete -a service-account.json -f manifestsFileOrFolder -v PROJECT_ID:test -v ZONE:europe-west1-b -v CLUSTER_NAME:test -v hashStable:COMMIT1 -v hashTesting:COMMIT2").
Expand Down
72 changes: 37 additions & 35 deletions pkg/provider/gke/gke.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package gke
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"log"
Expand All @@ -43,9 +42,9 @@ import (
)

// New is the GKE constructor.
func New() *GKE {
func New(dr *provider.DeploymentResource) *GKE {
return &GKE{
DeploymentVars: make(map[string]string),
DeploymentResource: dr,
}
}

Expand All @@ -62,11 +61,12 @@ type GKE struct {
clientGKE *gke.ClusterManagerClient
// The k8s provider used when we work with the manifest files.
k8sProvider *k8sProvider.K8s
// DeploymentFiles files provided from the cli.
// Final DeploymentFiles files.
DeploymentFiles []string
// Variables to substitute in the DeploymentFiles.
// These are also used when the command requires some variables that are not provided by the deployment file.
// Final DeploymentVars.
DeploymentVars map[string]string
// DeployResource to construct DeploymentVars and DeploymentFiles
DeploymentResource *provider.DeploymentResource
// Content bytes after parsing the template variables, grouped by filename.
gkeResources []Resource
// K8s resource.runtime objects after parsing the template variables, grouped by filename.
Expand Down Expand Up @@ -114,6 +114,7 @@ func (c *GKE) NewGKEClient(*kingpin.ParseContext) error {
// Set the auth env variable needed to the k8s client.
// The client looks for this special variable name and it is the only way to set the auth for now.
// TODO: Remove when the client supports an auth config option in NewDefaultClientConfig.
// https://github.com/kubernetes/kubernetes/pull/80303
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", saFile.Name())

opts := option.WithCredentialsJSON([]byte(c.Auth))
Expand All @@ -124,13 +125,26 @@ func (c *GKE) NewGKEClient(*kingpin.ParseContext) error {
}
c.clientGKE = cl
c.ctx = context.Background()

return nil
}

// SetupDeploymentResources Sets up DeploymentVars and DeploymentFiles
func (c *GKE) SetupDeploymentResources(*kingpin.ParseContext) error {
c.DeploymentFiles = c.DeploymentResource.DeploymentFiles
c.DeploymentVars = provider.MergeDeploymentVars(
c.DeploymentResource.DefaultDeploymentVars,
c.DeploymentResource.FlagDeploymentVars,
)
return nil
}

// GKEDeploymentsParse parses the cluster/nodepool deployment files and saves the result as bytes grouped by the filename.
// Any variables passed to the cli will be replaced in the resources files following the golang text template format.
func (c *GKE) GKEDeploymentsParse(*kingpin.ParseContext) error {
c.setProjectID()
if err := c.checkDeploymentVarsAndFiles(); err != nil {
return err
}

deploymentResource, err := provider.DeploymentsParse(c.DeploymentFiles, c.DeploymentVars)
if err != nil {
Expand All @@ -144,7 +158,9 @@ func (c *GKE) GKEDeploymentsParse(*kingpin.ParseContext) error {
// K8SDeploymentsParse parses the k8s objects deployment files and saves the result as k8s objects grouped by the filename.
// Any variables passed to the cli will be replaced in the resources files following the golang text template format.
func (c *GKE) K8SDeploymentsParse(*kingpin.ParseContext) error {
c.setProjectID()
if err := c.checkDeploymentVarsAndFiles(); err != nil {
return err
}

deploymentResource, err := provider.DeploymentsParse(c.DeploymentFiles, c.DeploymentVars)
if err != nil {
Expand Down Expand Up @@ -178,19 +194,18 @@ func (c *GKE) K8SDeploymentsParse(*kingpin.ParseContext) error {
return nil
}

// setProjectID either from the cli arg or read it from the auth data.
func (c *GKE) setProjectID() {
if v, ok := c.DeploymentVars["PROJECT_ID"]; !ok || v == "" {
d := make(map[string]interface{})
if err := json.Unmarshal([]byte(c.Auth), &d); err != nil {
log.Fatalf("Couldn't parse auth file: %v", err)
}
v, ok := d["project_id"].(string)
if !ok {
log.Fatal("Couldn't get project id from the auth file")
// checkDeploymentVarsAndFiles checks whether the requied deployment vars are passed.
func (c *GKE) checkDeploymentVarsAndFiles() error {
reqDepVars := []string{"PROJECT_ID", "ZONE", "CLUSTER_NAME"}
for _, k := range reqDepVars {
if v, ok := c.DeploymentVars[k]; !ok || v == "" {
return fmt.Errorf("missing required %v variable", k)
}
c.DeploymentVars["PROJECT_ID"] = v
}
if len(c.DeploymentFiles) == 0 {
return fmt.Errorf("missing deployment file(s)")
}
return nil
}

// ClusterCreate create a new cluster or applies changes to an existing cluster.
Expand Down Expand Up @@ -501,24 +516,11 @@ func (c *GKE) AllNodepoolsDeleted(*kingpin.ParseContext) error {

// NewK8sProvider sets the k8s provider used for deploying k8s manifests.
func (c *GKE) NewK8sProvider(*kingpin.ParseContext) error {
projectID, ok := c.DeploymentVars["PROJECT_ID"]
if !ok {
return fmt.Errorf("missing required PROJECT_ID variable")
}
zone, ok := c.DeploymentVars["ZONE"]
if !ok {
return fmt.Errorf("missing required ZONE variable")
}
clusterID, ok := c.DeploymentVars["CLUSTER_NAME"]
if !ok {
return fmt.Errorf("missing required CLUSTER_NAME variable")
}

// Get the authentication certificate for the cluster using the GKE client.
req := &containerpb.GetClusterRequest{
ProjectId: projectID,
Zone: zone,
ClusterId: clusterID,
ProjectId: c.DeploymentVars["PROJECT_ID"],
Zone: c.DeploymentVars["ZONE"],
ClusterId: c.DeploymentVars["CLUSTER_NAME"],
}
rep, err := c.clientGKE.GetCluster(c.ctx, req)
if err != nil {
Expand Down
30 changes: 30 additions & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,25 @@ const (
globalRetryTime = 10 * time.Second
)

// DeploymentResource holds list of variables and corresponding files.
type DeploymentResource struct {
// DeploymentFiles files provided from the cli.
DeploymentFiles []string
// DeploymentVars provided from the cli.
FlagDeploymentVars map[string]string
// Default DeploymentVars.
DefaultDeploymentVars map[string]string
}

// NewDeploymentResource returns DeploymentResource with default values.
func NewDeploymentResource() *DeploymentResource {
return &DeploymentResource{
DeploymentFiles: []string{},
FlagDeploymentVars: map[string]string{},
DefaultDeploymentVars: map[string]string{},
}
}

// Resource holds the file content after parsing the template variables.
type Resource struct {
FileName string
Expand Down Expand Up @@ -106,3 +125,14 @@ func DeploymentsParse(deploymentFiles []string, deploymentVars map[string]string
}
return deploymentObjects, nil
}

// MergeDeploymentVars merges multiple maps based on the order.
func MergeDeploymentVars(ms ...map[string]string) map[string]string {
res := map[string]string{}
for _, m := range ms {
for k, v := range m {
res[k] = v
}
}
return res
}
59 changes: 59 additions & 0 deletions pkg/provider/provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2020 The Prometheus 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 provider

import (
"reflect"
"testing"
)

func TestMergeDeploymentVars(t *testing.T) {
dv1 := map[string]string{
"foo": "apple",
"bar": "orange",
}
dv2 := map[string]string{
"foo": "mango",
"baz": "banana",
"buzz": "jackfruit",
}
dv3 := map[string]string{
"foo": "grape",
"baz": "blueberry",
}
testCases := []struct {
vars []map[string]string
merged map[string]string
}{
{
vars: []map[string]string{dv1, dv2, dv3},
merged: map[string]string{"bar": "orange", "baz": "blueberry", "buzz": "jackfruit", "foo": "grape"},
},
{
vars: []map[string]string{dv3, dv2, dv1},
merged: map[string]string{"bar": "orange", "baz": "banana", "buzz": "jackfruit", "foo": "apple"},
},
{
vars: []map[string]string{dv3, dv1, dv2},
merged: map[string]string{"bar": "orange", "baz": "banana", "buzz": "jackfruit", "foo": "mango"},
},
}

for _, tc := range testCases {
r := MergeDeploymentVars(tc.vars...)
if eq := reflect.DeepEqual(tc.merged, r); !eq {
t.Errorf("\nexpect %#v\ngot %#v", tc.merged, r)
}
}
}