<commit_before>/**
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * 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 cmds

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"strings"
	"time"

	"github.com/fabric8io/gofabric8/client"
	"github.com/fabric8io/gofabric8/util"
	oclient "github.com/openshift/origin/pkg/client"
	"github.com/openshift/origin/pkg/cmd/admin/policy"
	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
	"github.com/openshift/origin/pkg/template"
	tapi "github.com/openshift/origin/pkg/template/api"
	tapiv1 "github.com/openshift/origin/pkg/template/api/v1"
	"github.com/openshift/origin/pkg/template/generator"
	"github.com/spf13/cobra"
	"k8s.io/kubernetes/pkg/api"
	kapi "k8s.io/kubernetes/pkg/api"
	k8sclient "k8s.io/kubernetes/pkg/client"
	kcmd "k8s.io/kubernetes/pkg/kubectl/cmd"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/runtime"
)

const (
	consoleMetadataUrl           = "https://repo1.maven.org/maven2/io/fabric8/apps/base/maven-metadata.xml"
	baseConsoleUrl               = "https://repo1.maven.org/maven2/io/fabric8/apps/base/%[1]s/base-%[1]s-kubernetes.json"
	consoleKubernetesMetadataUrl = "https://repo1.maven.org/maven2/io/fabric8/apps/console-kubernetes/maven-metadata.xml"
	baseConsoleKubernetesUrl     = "https://repo1.maven.org/maven2/io/fabric8/apps/console-kubernetes/%[1]s/console-kubernetes-%[1]s-kubernetes.json"

	Fabric8SCC    = "fabric8"
	PrivilegedSCC = "privileged"
	RestrictedSCC = "restricted"
)

type createFunc func(c *k8sclient.Client, f *cmdutil.Factory, name string) (Result, error)

func NewCmdDeploy(f *cmdutil.Factory) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "deploy",
		Short: "Deploy fabric8 to your Kubernetes or OpenShift environment",
		Long:  `deploy fabric8 to your Kubernetes or OpenShift environment`,
		Run: func(cmd *cobra.Command, args []string) {
			c, cfg := client.NewClient(f)
			ns, _, _ := f.DefaultNamespace()
			util.Info("Deploying fabric8 to your ")
			util.Success(string(util.TypeOfMaster(c)))
			util.Info(" installation at ")
			util.Success(cfg.Host)
			util.Info(" in namespace ")
			util.Successf("%s\n\n", ns)

			if cmd.Flags().Lookup("yes").Value.String() == "false" {
				util.Info("Continue? [Y/n] ")
				cont := util.AskForConfirmation(true)
				if !cont {
					util.Fatal("Cancelled...\n")
				}
			}

			v := cmd.Flags().Lookup("version").Value.String()

			typeOfMaster := util.TypeOfMaster(c)
			v = f8Version(v, typeOfMaster)

			util.Warnf("\nStarting deployment of %s...\n\n", v)

			if typeOfMaster == util.Kubernetes {
				uri := fmt.Sprintf(baseConsoleKubernetesUrl, v)
				filenames := []string{uri}

				createCmd := cobra.Command{}
				createCmd.Flags().StringSlice("filename", filenames, "")
				err := kcmd.RunCreate(f, &createCmd, ioutil.Discard)
				if err != nil {
					printResult("fabric8 console", Failure, err)
				} else {
					printResult("fabric8 console", Success, nil)
				}
			} else {
				oc, _ := client.NewOpenShiftClient(cfg)

				r, err := verifyRestrictedSecurityContextConstraints(c, f)
				printResult("SecurityContextConstraints restricted", r, err)
				r, err = deployFabric8SecurityContextConstraints(c, f)
				printResult("SecurityContextConstraints fabric8", r, err)

				printAddClusterRoleToUser(oc, f, "cluster-admin", "system:serviceaccount:default:fabric8")
				printAddClusterRoleToUser(oc, f, "cluster-reader", "system:serviceaccount:default:metrics")

				printAddServiceAccount(c, f, "metrics")
				printAddServiceAccount(c, f, "router")

				uri := fmt.Sprintf(baseConsoleUrl, v)
				resp, err := http.Get(uri)
				if err != nil {
					util.Fatalf("Cannot get fabric8 template to deploy: %v", err)
				}
				defer resp.Body.Close()
				jsonData, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					util.Fatalf("Cannot get fabric8 template to deploy: %v", err)
				}
				var v1tmpl tapiv1.Template
				err = json.Unmarshal(jsonData, &v1tmpl)
				if err != nil {
					util.Fatalf("Cannot get fabric8 template to deploy: %v", err)
				}
				var tmpl tapi.Template

				err = api.Scheme.Convert(&v1tmpl, &tmpl)
				if err != nil {
					util.Fatalf("Cannot get fabric8 template to deploy: %v", err)
				}

				generators := map[string]generator.Generator{
					"expression": generator.NewExpressionValueGenerator(rand.New(rand.NewSource(time.Now().UnixNano()))),
				}
				p := template.NewProcessor(generators)

				tmpl.Parameters = append(tmpl.Parameters, tapi.Parameter{
					Name:  "DOMAIN",
					Value: cmd.Flags().Lookup("domain").Value.String(),
				})

				p.Process(&tmpl)

				for _, o := range tmpl.Objects {
					switch o := o.(type) {
					case *runtime.Unstructured:
						var b []byte
						b, err = json.Marshal(o.Object)
						if err != nil {
							break
						}
						req := c.Post().Body(b)
						if o.Kind != "OAuthClient" {
							req.Namespace(ns).Resource(strings.ToLower(o.TypeMeta.Kind + "s"))
						} else {
							req.AbsPath("oapi", "v1", strings.ToLower(o.TypeMeta.Kind+"s"))
						}
						res := req.Do()
						if res.Error() != nil {
							err = res.Error()
							break
						}
						var statusCode int
						res.StatusCode(&statusCode)
						if statusCode != http.StatusCreated {
							err = fmt.Errorf("Failed to create %s: %d", o.TypeMeta.Kind, statusCode)
							break
						}
					}
				}

				if err != nil {
					printResult("fabric8 console", Failure, err)
				} else {
					printResult("fabric8 console", Success, nil)
				}
			}
		},
	}
	cmd.PersistentFlags().StringP("domain", "d", "vagrant.f8", "The domain name to append to the service name to access web applications")
	return cmd
}

func deployFabric8SecurityContextConstraints(c *k8sclient.Client, f *cmdutil.Factory) (Result, error) {
	name := Fabric8SCC
	scc := kapi.SecurityContextConstraints{
		ObjectMeta: kapi.ObjectMeta{
			Name: name,
		},
		AllowPrivilegedContainer: true,
		AllowHostDirVolumePlugin: true,
		SELinuxContext: kapi.SELinuxContextStrategyOptions{
			Type: kapi.SELinuxStrategyRunAsAny,
		},
		RunAsUser: kapi.RunAsUserStrategyOptions{
			Type: kapi.RunAsUserStrategyRunAsAny,
		},
		Users:  []string{"system:serviceaccount:openshift-infra:build-controller", "system:serviceaccount:default:default", "system:serviceaccount:default:fabric8", "system:serviceaccount:default:jenkins", "system:serviceaccount:default:router"},
		Groups: []string{bootstrappolicy.ClusterAdminGroup, bootstrappolicy.NodesGroup},
	}
	ns, _, err := f.DefaultNamespace()
	if err != nil {
		util.Fatal("No default namespace")
		return Failure, err
	}
	_, err = c.SecurityContextConstraints().Get(name)
	if err == nil {
		err = c.SecurityContextConstraints().Delete(name)
		if err != nil {
			return Failure, err
		}
	}
	_, err = c.SecurityContextConstraints().Create(&scc)
	if err != nil {
		util.Fatalf("Cannot create SecurityContextConstraints: %v\n", err)
		util.Fatalf("Failed to create SecurityContextConstraints %v in namespace %s: %v\n", scc, ns, err)
		return Failure, err
	}
	util.Infof("SecurityContextConstraints %s is setup correctly\n", name)
	return Success, err
}

// Ensure that the `restricted` SecurityContextConstraints has the RunAsUser set to RunAsAny
//
// if `restricted does not exist lets create it
// otherwise if needed lets modify the RunAsUser
func verifyRestrictedSecurityContextConstraints(c *k8sclient.Client, f *cmdutil.Factory) (Result, error) {
	name := RestrictedSCC
	ns, _, e := f.DefaultNamespace()
	if e != nil {
		util.Fatal("No default namespace")
		return Failure, e
	}
	rc, err := c.SecurityContextConstraints().Get(name)
	if err != nil {
		scc := kapi.SecurityContextConstraints{
			ObjectMeta: kapi.ObjectMeta{
				Name: RestrictedSCC,
			},
			SELinuxContext: kapi.SELinuxContextStrategyOptions{
				Type: kapi.SELinuxStrategyMustRunAs,
			},
			RunAsUser: kapi.RunAsUserStrategyOptions{
				Type: kapi.RunAsUserStrategyRunAsAny,
			},
			Groups: []string{bootstrappolicy.AuthenticatedGroup},
		}

		_, err = c.SecurityContextConstraints().Create(&scc)
		if err != nil {
			return Failure, err
		} else {
			util.Infof("SecurityContextConstraints %s created\n", name)
			return Success, err
		}
	}
	// lets check that the restricted is configured corectly
	if kapi.RunAsUserStrategyRunAsAny != rc.RunAsUser.Type {
		rc.RunAsUser.Type = kapi.RunAsUserStrategyRunAsAny
		_, err = c.SecurityContextConstraints().Update(rc)
		if err != nil {
			util.Fatalf("Failed to update SecurityContextConstraints %v in namespace %s: %v\n", rc, ns, err)
			return Failure, err
		}
		util.Infof("SecurityContextConstraints %s is updated to enable fabric8\n", name)
	} else {
		util.Infof("SecurityContextConstraints %s is configured correctly\n", name)
	}
	return Success, err
}

func printAddServiceAccount(c *k8sclient.Client, f *cmdutil.Factory, name string) (Result, error) {
	r, err := addServiceAccount(c, f, name)
	message := fmt.Sprintf("addServiceAccount %s", name)
	printResult(message, r, err)
	return r, err
}

func addServiceAccount(c *k8sclient.Client, f *cmdutil.Factory, name string) (Result, error) {
	ns, _, e := f.DefaultNamespace()
	if e != nil {
		util.Fatal("No default namespace")
		return Failure, e
	}
	sas := c.ServiceAccounts(ns)
	_, err := sas.Get(name)
	if err != nil {
		sa := kapi.ServiceAccount{
			ObjectMeta: kapi.ObjectMeta{
				Name: name,
			},
		}
		_, err = sas.Create(&sa)
	}
	r := Success
	if err != nil {
		r = Failure
	}
	return r, err
}

func printAddClusterRoleToUser(c *oclient.Client, f *cmdutil.Factory, roleName string, userName string) (Result, error) {
	err := addClusterRoleToUser(c, f, roleName, userName)
	message := fmt.Sprintf("addClusterRoleToUser %s %s", roleName, userName)
	r := Success
	if err != nil {
		r = Failure
	}
	printResult(message, r, err)
	return r, err
}

// simulates: oadm policy add-cluster-role-to-user roleName userName
func addClusterRoleToUser(c *oclient.Client, f *cmdutil.Factory, roleName string, userName string) error {
	namespace, _, err := f.DefaultNamespace()
	if err != nil {
		util.Info("No namespace!'\n")
		return err
	}
	options := policy.RoleModificationOptions{
		RoleName:            roleName,
		RoleBindingAccessor: policy.NewLocalRoleBindingAccessor(namespace, c),
		Users:               []string{userName},
	}
	return options.AddRole()
}

func f8Version(v string, typeOfMaster util.MasterType) string {
	metadataUrl := consoleMetadataUrl
	if typeOfMaster == util.Kubernetes {
		metadataUrl = consoleKubernetesMetadataUrl
	}

	resp, err := http.Get(metadataUrl)
	if err != nil {
		util.Fatalf("Cannot get fabric8 version to deploy: %v", err)
	}
	defer resp.Body.Close()
	// read xml http response
	xmlData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		util.Fatalf("Cannot get fabric8 version to deploy: %v", err)
	}

	type Metadata struct {
		Release  string   `xml:"versioning>release"`
		Versions []string `xml:"versioning>versions>version"`
	}

	var m Metadata
	err = xml.Unmarshal(xmlData, &m)
	if err != nil {
		util.Fatalf("Cannot get fabric8 version to deploy: %v", err)
	}

	if v == "latest" {
		return m.Release
	}

	for _, version := range m.Versions {
		if v == version {
			return version
		}
	}

	util.Errorf("\nUnknown version: %s\n", v)
	util.Fatalf("Valid versions: %v\n", append(m.Versions, "latest"))
	return ""
}
<commit_msg>fix so we can create the router<commit_after>/**
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * 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 cmds

import (
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"strings"
	"time"

	"github.com/fabric8io/gofabric8/client"
	"github.com/fabric8io/gofabric8/util"
	oclient "github.com/openshift/origin/pkg/client"
	"github.com/openshift/origin/pkg/cmd/admin/policy"
	"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
	"github.com/openshift/origin/pkg/template"
	tapi "github.com/openshift/origin/pkg/template/api"
	tapiv1 "github.com/openshift/origin/pkg/template/api/v1"
	"github.com/openshift/origin/pkg/template/generator"
	"github.com/spf13/cobra"
	"k8s.io/kubernetes/pkg/api"
	kapi "k8s.io/kubernetes/pkg/api"
	k8sclient "k8s.io/kubernetes/pkg/client"
	kcmd "k8s.io/kubernetes/pkg/kubectl/cmd"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/runtime"
)

const (
	consoleMetadataUrl           = "https://repo1.maven.org/maven2/io/fabric8/apps/base/maven-metadata.xml"
	baseConsoleUrl               = "https://repo1.maven.org/maven2/io/fabric8/apps/base/%[1]s/base-%[1]s-kubernetes.json"
	consoleKubernetesMetadataUrl = "https://repo1.maven.org/maven2/io/fabric8/apps/console-kubernetes/maven-metadata.xml"
	baseConsoleKubernetesUrl     = "https://repo1.maven.org/maven2/io/fabric8/apps/console-kubernetes/%[1]s/console-kubernetes-%[1]s-kubernetes.json"

	Fabric8SCC    = "fabric8"
	PrivilegedSCC = "privileged"
	RestrictedSCC = "restricted"
)

type createFunc func(c *k8sclient.Client, f *cmdutil.Factory, name string) (Result, error)

func NewCmdDeploy(f *cmdutil.Factory) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "deploy",
		Short: "Deploy fabric8 to your Kubernetes or OpenShift environment",
		Long:  `deploy fabric8 to your Kubernetes or OpenShift environment`,
		Run: func(cmd *cobra.Command, args []string) {
			c, cfg := client.NewClient(f)
			ns, _, _ := f.DefaultNamespace()
			util.Info("Deploying fabric8 to your ")
			util.Success(string(util.TypeOfMaster(c)))
			util.Info(" installation at ")
			util.Success(cfg.Host)
			util.Info(" in namespace ")
			util.Successf("%s\n\n", ns)

			if cmd.Flags().Lookup("yes").Value.String() == "false" {
				util.Info("Continue? [Y/n] ")
				cont := util.AskForConfirmation(true)
				if !cont {
					util.Fatal("Cancelled...\n")
				}
			}

			v := cmd.Flags().Lookup("version").Value.String()

			typeOfMaster := util.TypeOfMaster(c)
			v = f8Version(v, typeOfMaster)

			util.Warnf("\nStarting deployment of %s...\n\n", v)

			if typeOfMaster == util.Kubernetes {
				uri := fmt.Sprintf(baseConsoleKubernetesUrl, v)
				filenames := []string{uri}

				createCmd := cobra.Command{}
				createCmd.Flags().StringSlice("filename", filenames, "")
				err := kcmd.RunCreate(f, &createCmd, ioutil.Discard)
				if err != nil {
					printResult("fabric8 console", Failure, err)
				} else {
					printResult("fabric8 console", Success, nil)
				}
			} else {
				oc, _ := client.NewOpenShiftClient(cfg)

				r, err := verifyRestrictedSecurityContextConstraints(c, f)
				printResult("SecurityContextConstraints restricted", r, err)
				r, err = deployFabric8SecurityContextConstraints(c, f)
				printResult("SecurityContextConstraints fabric8", r, err)

				printAddClusterRoleToUser(oc, f, "cluster-admin", "system:serviceaccount:default:fabric8")
				printAddClusterRoleToUser(oc, f, "cluster-reader", "system:serviceaccount:default:metrics")

				printAddServiceAccount(c, f, "metrics")
				printAddServiceAccount(c, f, "router")

				uri := fmt.Sprintf(baseConsoleUrl, v)
				resp, err := http.Get(uri)
				if err != nil {
					util.Fatalf("Cannot get fabric8 template to deploy: %v", err)
				}
				defer resp.Body.Close()
				jsonData, err := ioutil.ReadAll(resp.Body)
				if err != nil {
					util.Fatalf("Cannot get fabric8 template to deploy: %v", err)
				}
				var v1tmpl tapiv1.Template
				err = json.Unmarshal(jsonData, &v1tmpl)
				if err != nil {
					util.Fatalf("Cannot get fabric8 template to deploy: %v", err)
				}
				var tmpl tapi.Template

				err = api.Scheme.Convert(&v1tmpl, &tmpl)
				if err != nil {
					util.Fatalf("Cannot get fabric8 template to deploy: %v", err)
				}

				generators := map[string]generator.Generator{
					"expression": generator.NewExpressionValueGenerator(rand.New(rand.NewSource(time.Now().UnixNano()))),
				}
				p := template.NewProcessor(generators)

				tmpl.Parameters = append(tmpl.Parameters, tapi.Parameter{
					Name:  "DOMAIN",
					Value: cmd.Flags().Lookup("domain").Value.String(),
				})

				p.Process(&tmpl)

				for _, o := range tmpl.Objects {
					switch o := o.(type) {
					case *runtime.Unstructured:
						var b []byte
						b, err = json.Marshal(o.Object)
						if err != nil {
							break
						}
						req := c.Post().Body(b)
						if o.Kind != "OAuthClient" {
							req.Namespace(ns).Resource(strings.ToLower(o.TypeMeta.Kind + "s"))
						} else {
							req.AbsPath("oapi", "v1", strings.ToLower(o.TypeMeta.Kind+"s"))
						}
						res := req.Do()
						if res.Error() != nil {
							err = res.Error()
							break
						}
						var statusCode int
						res.StatusCode(&statusCode)
						if statusCode != http.StatusCreated {
							err = fmt.Errorf("Failed to create %s: %d", o.TypeMeta.Kind, statusCode)
							break
						}
					}
				}

				if err != nil {
					printResult("fabric8 console", Failure, err)
				} else {
					printResult("fabric8 console", Success, nil)
				}
			}
		},
	}
	cmd.PersistentFlags().StringP("domain", "d", "vagrant.f8", "The domain name to append to the service name to access web applications")
	return cmd
}

func deployFabric8SecurityContextConstraints(c *k8sclient.Client, f *cmdutil.Factory) (Result, error) {
	name := Fabric8SCC
	scc := kapi.SecurityContextConstraints{
		ObjectMeta: kapi.ObjectMeta{
			Name: name,
		},
		AllowPrivilegedContainer: true,
		AllowHostPorts: true,
		AllowHostDirVolumePlugin: true,
		SELinuxContext: kapi.SELinuxContextStrategyOptions{
			Type: kapi.SELinuxStrategyRunAsAny,
		},
		RunAsUser: kapi.RunAsUserStrategyOptions{
			Type: kapi.RunAsUserStrategyRunAsAny,
		},
		Users:  []string{"system:serviceaccount:openshift-infra:build-controller", "system:serviceaccount:default:default", "system:serviceaccount:default:fabric8", "system:serviceaccount:default:jenkins", "system:serviceaccount:default:router"},
		Groups: []string{bootstrappolicy.ClusterAdminGroup, bootstrappolicy.NodesGroup},
	}
	ns, _, err := f.DefaultNamespace()
	if err != nil {
		util.Fatal("No default namespace")
		return Failure, err
	}
	_, err = c.SecurityContextConstraints().Get(name)
	if err == nil {
		err = c.SecurityContextConstraints().Delete(name)
		if err != nil {
			return Failure, err
		}
	}
	_, err = c.SecurityContextConstraints().Create(&scc)
	if err != nil {
		util.Fatalf("Cannot create SecurityContextConstraints: %v\n", err)
		util.Fatalf("Failed to create SecurityContextConstraints %v in namespace %s: %v\n", scc, ns, err)
		return Failure, err
	}
	util.Infof("SecurityContextConstraints %s is setup correctly\n", name)
	return Success, err
}

// Ensure that the `restricted` SecurityContextConstraints has the RunAsUser set to RunAsAny
//
// if `restricted does not exist lets create it
// otherwise if needed lets modify the RunAsUser
func verifyRestrictedSecurityContextConstraints(c *k8sclient.Client, f *cmdutil.Factory) (Result, error) {
	name := RestrictedSCC
	ns, _, e := f.DefaultNamespace()
	if e != nil {
		util.Fatal("No default namespace")
		return Failure, e
	}
	rc, err := c.SecurityContextConstraints().Get(name)
	if err != nil {
		scc := kapi.SecurityContextConstraints{
			ObjectMeta: kapi.ObjectMeta{
				Name: RestrictedSCC,
			},
			SELinuxContext: kapi.SELinuxContextStrategyOptions{
				Type: kapi.SELinuxStrategyMustRunAs,
			},
			RunAsUser: kapi.RunAsUserStrategyOptions{
				Type: kapi.RunAsUserStrategyRunAsAny,
			},
			Groups: []string{bootstrappolicy.AuthenticatedGroup},
		}

		_, err = c.SecurityContextConstraints().Create(&scc)
		if err != nil {
			return Failure, err
		} else {
			util.Infof("SecurityContextConstraints %s created\n", name)
			return Success, err
		}
	}

	// lets check that the restricted is configured correctly
	if kapi.RunAsUserStrategyRunAsAny != rc.RunAsUser.Type {
		rc.RunAsUser.Type = kapi.RunAsUserStrategyRunAsAny
		_, err = c.SecurityContextConstraints().Update(rc)
		if err != nil {
			util.Fatalf("Failed to update SecurityContextConstraints %v in namespace %s: %v\n", rc, ns, err)
			return Failure, err
		}
		util.Infof("SecurityContextConstraints %s is updated to enable fabric8\n", name)
	} else {
		util.Infof("SecurityContextConstraints %s is configured correctly\n", name)
	}
	return Success, err
}

func printAddServiceAccount(c *k8sclient.Client, f *cmdutil.Factory, name string) (Result, error) {
	r, err := addServiceAccount(c, f, name)
	message := fmt.Sprintf("addServiceAccount %s", name)
	printResult(message, r, err)
	return r, err
}

func addServiceAccount(c *k8sclient.Client, f *cmdutil.Factory, name string) (Result, error) {
	ns, _, e := f.DefaultNamespace()
	if e != nil {
		util.Fatal("No default namespace")
		return Failure, e
	}
	sas := c.ServiceAccounts(ns)
	_, err := sas.Get(name)
	if err != nil {
		sa := kapi.ServiceAccount{
			ObjectMeta: kapi.ObjectMeta{
				Name: name,
			},
		}
		_, err = sas.Create(&sa)
	}
	r := Success
	if err != nil {
		r = Failure
	}
	return r, err
}

func printAddClusterRoleToUser(c *oclient.Client, f *cmdutil.Factory, roleName string, userName string) (Result, error) {
	err := addClusterRoleToUser(c, f, roleName, userName)
	message := fmt.Sprintf("addClusterRoleToUser %s %s", roleName, userName)
	r := Success
	if err != nil {
		r = Failure
	}
	printResult(message, r, err)
	return r, err
}

// simulates: oadm policy add-cluster-role-to-user roleName userName
func addClusterRoleToUser(c *oclient.Client, f *cmdutil.Factory, roleName string, userName string) error {
	namespace, _, err := f.DefaultNamespace()
	if err != nil {
		util.Info("No namespace!'\n")
		return err
	}
	options := policy.RoleModificationOptions{
		RoleName:            roleName,
		RoleBindingAccessor: policy.NewLocalRoleBindingAccessor(namespace, c),
		Users:               []string{userName},
	}
	return options.AddRole()
}

func f8Version(v string, typeOfMaster util.MasterType) string {
	metadataUrl := consoleMetadataUrl
	if typeOfMaster == util.Kubernetes {
		metadataUrl = consoleKubernetesMetadataUrl
	}

	resp, err := http.Get(metadataUrl)
	if err != nil {
		util.Fatalf("Cannot get fabric8 version to deploy: %v", err)
	}
	defer resp.Body.Close()
	// read xml http response
	xmlData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		util.Fatalf("Cannot get fabric8 version to deploy: %v", err)
	}

	type Metadata struct {
		Release  string   `xml:"versioning>release"`
		Versions []string `xml:"versioning>versions>version"`
	}

	var m Metadata
	err = xml.Unmarshal(xmlData, &m)
	if err != nil {
		util.Fatalf("Cannot get fabric8 version to deploy: %v", err)
	}

	if v == "latest" {
		return m.Release
	}

	for _, version := range m.Versions {
		if v == version {
			return version
		}
	}

	util.Errorf("\nUnknown version: %s\n", v)
	util.Fatalf("Valid versions: %v\n", append(m.Versions, "latest"))
	return ""
}
<|endoftext|>


<commit_before>package detailed_test

import (
	"fmt"
	"testing"

	"github.com/weaveworks/scope/probe/docker"
	"github.com/weaveworks/scope/probe/host"
	"github.com/weaveworks/scope/probe/kubernetes"
	"github.com/weaveworks/scope/probe/process"
	"github.com/weaveworks/scope/render"
	"github.com/weaveworks/scope/render/detailed"
	"github.com/weaveworks/scope/render/expected"
	"github.com/weaveworks/scope/report"
	"github.com/weaveworks/scope/test"
	"github.com/weaveworks/scope/test/fixture"
	"github.com/weaveworks/scope/test/reflect"
)

func child(t *testing.T, r render.Renderer, id string) detailed.NodeSummary {
	s, ok := detailed.MakeNodeSummary(fixture.Report, r.Render(fixture.Report, nil)[id])
	if !ok {
		t.Fatalf("Expected node %s to be summarizable, but wasn't", id)
	}
	return s.SummarizeMetrics()
}

func TestMakeDetailedHostNode(t *testing.T) {
	renderableNodes := render.HostRenderer.Render(fixture.Report, nil)
	renderableNode := renderableNodes[fixture.ClientHostNodeID]
	have := detailed.MakeNode("hosts", fixture.Report, renderableNodes, renderableNode)

	containerImageNodeSummary := child(t, render.ContainerImageRenderer, expected.ClientContainerImageNodeID)
	containerNodeSummary := child(t, render.ContainerRenderer, fixture.ClientContainerNodeID)
	process1NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess1NodeID)
	process1NodeSummary.Linkable = true
	process2NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess2NodeID)
	process2NodeSummary.Linkable = true
	podNodeSummary := child(t, render.PodRenderer, fixture.ClientPodNodeID)
	want := detailed.Node{
		NodeSummary: detailed.NodeSummary{
			ID:         fixture.ClientHostNodeID,
			Label:      "client",
			LabelMinor: "hostname.com",
			Rank:       "hostname.com",
			Pseudo:     false,
			Shape:      "circle",
			Linkable:   true,
			Adjacency:  report.MakeIDList(fixture.ServerHostNodeID),
			Metadata: []report.MetadataRow{
				{
					ID:       "host_name",
					Label:    "Hostname",
					Value:    "client.hostname.com",
					Priority: 11,
				},
				{
					ID:       "os",
					Label:    "OS",
					Value:    "Linux",
					Priority: 12,
				},
				{
					ID:       "local_networks",
					Label:    "Local Networks",
					Value:    "10.10.10.0/24",
					Priority: 13,
				},
			},
			Metrics: []report.MetricRow{
				{
					ID:       host.CPUUsage,
					Label:    "CPU",
					Format:   "percent",
					Value:    0.07,
					Priority: 1,
					Metric:   &fixture.ClientHostCPUMetric,
				},
				{
					ID:       host.MemoryUsage,
					Label:    "Memory",
					Format:   "filesize",
					Value:    0.08,
					Priority: 2,
					Metric:   &fixture.ClientHostMemoryMetric,
				},
				{
					ID:       host.Load1,
					Label:    "Load (1m)",
					Group:    "load",
					Value:    0.09,
					Priority: 11,
					Metric:   &fixture.ClientHostLoad1Metric,
				},
			},
		},
		Controls: []detailed.ControlInstance{},
		Children: []detailed.NodeSummaryGroup{
			{
				Label:      "Pods",
				TopologyID: "pods",
				Columns: []detailed.Column{
					{ID: kubernetes.State, Label: "State"},
					{ID: report.Container, Label: "Containers"},
					{ID: kubernetes.IP, Label: "IP"},
				},
				Nodes: []detailed.NodeSummary{podNodeSummary},
			},
			{
				Label:      "Containers",
				TopologyID: "containers",
				Columns: []detailed.Column{
					{ID: docker.CPUTotalUsage, Label: "CPU"},
					{ID: docker.MemoryUsage, Label: "Memory"},
				},
				Nodes: []detailed.NodeSummary{containerNodeSummary},
			},
			{
				Label:      "Processes",
				TopologyID: "processes",
				Columns: []detailed.Column{
					{ID: process.PID, Label: "PID"},
					{ID: process.CPUUsage, Label: "CPU"},
					{ID: process.MemoryUsage, Label: "Memory"},
				},
				Nodes: []detailed.NodeSummary{process1NodeSummary, process2NodeSummary},
			},
			{
				Label:      "Container Images",
				TopologyID: "containers-by-image",
				Columns: []detailed.Column{
					{ID: report.Container, Label: "# Containers", DefaultSort: true},
				},
				Nodes: []detailed.NodeSummary{containerImageNodeSummary},
			},
		},
		Connections: []detailed.ConnectionsSummary{
			{
				ID:          "incoming-connections",
				TopologyID:  "hosts",
				Label:       "Inbound",
				Columns:     detailed.NormalColumns,
				Connections: []detailed.Connection{},
			},
			{
				ID:         "outgoing-connections",
				TopologyID: "hosts",
				Label:      "Outbound",
				Columns:    detailed.NormalColumns,
				Connections: []detailed.Connection{
					{
						ID:       fmt.Sprintf("%s:%s-%s:%s-%d", fixture.ServerHostNodeID, "", fixture.ClientHostNodeID, "", 80),
						NodeID:   fixture.ServerHostNodeID,
						Label:    "server",
						Linkable: true,
						Metadata: []report.MetadataRow{
							{
								ID:       "port",
								Value:    "80",
								Datatype: "number",
							},
							{
								ID:       "count",
								Value:    "2",
								Datatype: "number",
							},
						},
					},
				},
			},
		},
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}

func TestMakeDetailedContainerNode(t *testing.T) {
	id := fixture.ServerContainerNodeID
	renderableNodes := render.ContainerWithImageNameRenderer.Render(fixture.Report, nil)
	renderableNode, ok := renderableNodes[id]
	if !ok {
		t.Fatalf("Node not found: %s", id)
	}
	have := detailed.MakeNode("containers", fixture.Report, renderableNodes, renderableNode)

	serverProcessNodeSummary := child(t, render.ProcessRenderer, fixture.ServerProcessNodeID)
	serverProcessNodeSummary.Linkable = true
	want := detailed.Node{
		NodeSummary: detailed.NodeSummary{
			ID:         id,
			Label:      "server",
			LabelMinor: "server.hostname.com",
			Rank:       fixture.ServerContainerImageName,
			Shape:      "hexagon",
			Linkable:   true,
			Pseudo:     false,
			Metadata: []report.MetadataRow{
				{ID: "docker_container_id", Label: "ID", Value: fixture.ServerContainerID, Priority: 1},
				{ID: "docker_container_state_human", Label: "State", Value: "running", Priority: 2},
				{ID: "docker_image_id", Label: "Image ID", Value: fixture.ServerContainerImageID, Priority: 11},
			},
			Metrics: []report.MetricRow{
				{
					ID:       docker.CPUTotalUsage,
					Label:    "CPU",
					Format:   "percent",
					Value:    0.05,
					Priority: 1,
					Metric:   &fixture.ServerContainerCPUMetric,
				},
				{
					ID:       docker.MemoryUsage,
					Label:    "Memory",
					Format:   "filesize",
					Value:    0.06,
					Priority: 2,
					Metric:   &fixture.ServerContainerMemoryMetric,
				},
			},
			Parents: []detailed.Parent{
				{
					ID:         expected.ServerContainerImageNodeID,
					Label:      fixture.ServerContainerImageName,
					TopologyID: "containers-by-image",
				},
				{
					ID:         fixture.ServerHostNodeID,
					Label:      fixture.ServerHostName,
					TopologyID: "hosts",
				},
				{
					ID:         fixture.ServerPodNodeID,
					Label:      "pong-b",
					TopologyID: "pods",
				},
			},
		},
		Controls: []detailed.ControlInstance{},
		Children: []detailed.NodeSummaryGroup{
			{
				Label:      "Processes",
				TopologyID: "processes",
				Columns: []detailed.Column{
					{ID: process.PID, Label: "PID"},
					{ID: process.CPUUsage, Label: "CPU"},
					{ID: process.MemoryUsage, Label: "Memory"},
				},
				Nodes: []detailed.NodeSummary{serverProcessNodeSummary},
			},
		},
		Connections: []detailed.ConnectionsSummary{
			{
				ID:         "incoming-connections",
				TopologyID: "containers",
				Label:      "Inbound",
				Columns:    detailed.NormalColumns,
				Connections: []detailed.Connection{
					{
						ID:       fmt.Sprintf("%s:%s-%s:%s-%d", fixture.ClientContainerNodeID, "", fixture.ServerContainerNodeID, "", 80),
						NodeID:   fixture.ClientContainerNodeID,
						Label:    "client",
						Linkable: true,
						Metadata: []report.MetadataRow{
							{
								ID:       "port",
								Value:    "80",
								Datatype: "number",
							},
							{
								ID:       "count",
								Value:    "2",
								Datatype: "number",
							},
						},
					},
					{
						ID:       fmt.Sprintf("%s:%s-%s:%s-%d", render.IncomingInternetID, "", fixture.ServerContainerNodeID, "", 80),
						NodeID:   render.IncomingInternetID,
						Label:    render.InboundMajor,
						Linkable: true,
						Metadata: []report.MetadataRow{
							{
								ID:       "port",
								Value:    "80",
								Datatype: "number",
							},
							{
								ID:       "count",
								Value:    "1",
								Datatype: "number",
							},
						},
					},
				},
			},
			{
				ID:          "outgoing-connections",
				TopologyID:  "containers",
				Label:       "Outbound",
				Columns:     detailed.NormalColumns,
				Connections: []detailed.Connection{},
			},
		},
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}

func TestMakeDetailedPodNode(t *testing.T) {
	id := fixture.ServerPodNodeID
	renderableNodes := render.PodRenderer.Render(fixture.Report, nil)
	renderableNode, ok := renderableNodes[id]
	if !ok {
		t.Fatalf("Node not found: %s", id)
	}
	have := detailed.MakeNode("pods", fixture.Report, renderableNodes, renderableNode)

	containerNodeSummary := child(t, render.ContainerWithImageNameRenderer, fixture.ServerContainerNodeID)
	serverProcessNodeSummary := child(t, render.ProcessRenderer, fixture.ServerProcessNodeID)
	serverProcessNodeSummary.Linkable = true // Temporary workaround for: https://github.com/weaveworks/scope/issues/1295
	want := detailed.Node{
		NodeSummary: detailed.NodeSummary{
			ID:         id,
			Label:      "pong-b",
			LabelMinor: "1 container",
			Rank:       "ping/pong-b",
			Shape:      "heptagon",
			Linkable:   true,
			Pseudo:     false,
			Metadata: []report.MetadataRow{
				{ID: "kubernetes_state", Label: "State", Value: "running", Priority: 2},
				{ID: "container", Label: "# Containers", Value: "1", Priority: 4, Datatype: "number"},
				{ID: "kubernetes_namespace", Label: "Namespace", Value: "ping", Priority: 5},
			},
			Parents: []detailed.Parent{
				{
					ID:         fixture.ServerHostNodeID,
					Label:      fixture.ServerHostName,
					TopologyID: "hosts",
				},
				{
					ID:         fixture.ServiceNodeID,
					Label:      fixture.ServiceName,
					TopologyID: "services",
				},
			},
		},
		Controls: []detailed.ControlInstance{},
		Children: []detailed.NodeSummaryGroup{
			{
				Label:      "Containers",
				TopologyID: "containers",
				Columns: []detailed.Column{
					{ID: docker.CPUTotalUsage, Label: "CPU"},
					{ID: docker.MemoryUsage, Label: "Memory"},
				},
				Nodes: []detailed.NodeSummary{containerNodeSummary},
			},
			{
				Label:      "Processes",
				TopologyID: "processes",
				Columns: []detailed.Column{
					{ID: process.PID, Label: "PID"},
					{ID: process.CPUUsage, Label: "CPU"},
					{ID: process.MemoryUsage, Label: "Memory"},
				},
				Nodes: []detailed.NodeSummary{serverProcessNodeSummary},
			},
		},
		Connections: []detailed.ConnectionsSummary{
			{
				ID:         "incoming-connections",
				TopologyID: "pods",
				Label:      "Inbound",
				Columns:    detailed.NormalColumns,
				Connections: []detailed.Connection{
					{
						ID:       fmt.Sprintf("%s:%s-%s:%s-%d", fixture.ClientPodNodeID, "", fixture.ServerPodNodeID, "", 80),
						NodeID:   fixture.ClientPodNodeID,
						Label:    "pong-a",
						Linkable: true,
						Metadata: []report.MetadataRow{
							{
								ID:       "port",
								Value:    "80",
								Datatype: "number",
							},
							{
								ID:       "count",
								Value:    "2",
								Datatype: "number",
							},
						},
					},
					{
						ID:       fmt.Sprintf("%s:%s-%s:%s-%d", render.IncomingInternetID, "", fixture.ServerPodNodeID, "", 80),
						NodeID:   render.IncomingInternetID,
						Label:    render.InboundMajor,
						Linkable: true,
						Metadata: []report.MetadataRow{
							{
								ID:       "port",
								Value:    "80",
								Datatype: "number",
							},
							{
								ID:       "count",
								Value:    "1",
								Datatype: "number",
							},
						},
					},
				},
			},
			{
				ID:          "outgoing-connections",
				TopologyID:  "pods",
				Label:       "Outbound",
				Columns:     detailed.NormalColumns,
				Connections: []detailed.Connection{},
			},
		},
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}
<commit_msg>fix unit tests<commit_after>package detailed_test

import (
	"fmt"
	"testing"

	"github.com/weaveworks/scope/probe/docker"
	"github.com/weaveworks/scope/probe/host"
	"github.com/weaveworks/scope/probe/kubernetes"
	"github.com/weaveworks/scope/probe/process"
	"github.com/weaveworks/scope/render"
	"github.com/weaveworks/scope/render/detailed"
	"github.com/weaveworks/scope/render/expected"
	"github.com/weaveworks/scope/report"
	"github.com/weaveworks/scope/test"
	"github.com/weaveworks/scope/test/fixture"
	"github.com/weaveworks/scope/test/reflect"
)

func child(t *testing.T, r render.Renderer, id string) detailed.NodeSummary {
	s, ok := detailed.MakeNodeSummary(fixture.Report, r.Render(fixture.Report, nil)[id])
	if !ok {
		t.Fatalf("Expected node %s to be summarizable, but wasn't", id)
	}
	return s.SummarizeMetrics()
}

func connectionID(nodeID string) string {
	return fmt.Sprintf("%s-%s-%d", nodeID, "", 80)
}

func TestMakeDetailedHostNode(t *testing.T) {
	renderableNodes := render.HostRenderer.Render(fixture.Report, nil)
	renderableNode := renderableNodes[fixture.ClientHostNodeID]
	have := detailed.MakeNode("hosts", fixture.Report, renderableNodes, renderableNode)

	containerImageNodeSummary := child(t, render.ContainerImageRenderer, expected.ClientContainerImageNodeID)
	containerNodeSummary := child(t, render.ContainerRenderer, fixture.ClientContainerNodeID)
	process1NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess1NodeID)
	process1NodeSummary.Linkable = true
	process2NodeSummary := child(t, render.ProcessRenderer, fixture.ClientProcess2NodeID)
	process2NodeSummary.Linkable = true
	podNodeSummary := child(t, render.PodRenderer, fixture.ClientPodNodeID)
	want := detailed.Node{
		NodeSummary: detailed.NodeSummary{
			ID:         fixture.ClientHostNodeID,
			Label:      "client",
			LabelMinor: "hostname.com",
			Rank:       "hostname.com",
			Pseudo:     false,
			Shape:      "circle",
			Linkable:   true,
			Adjacency:  report.MakeIDList(fixture.ServerHostNodeID),
			Metadata: []report.MetadataRow{
				{
					ID:       "host_name",
					Label:    "Hostname",
					Value:    "client.hostname.com",
					Priority: 11,
				},
				{
					ID:       "os",
					Label:    "OS",
					Value:    "Linux",
					Priority: 12,
				},
				{
					ID:       "local_networks",
					Label:    "Local Networks",
					Value:    "10.10.10.0/24",
					Priority: 13,
				},
			},
			Metrics: []report.MetricRow{
				{
					ID:       host.CPUUsage,
					Label:    "CPU",
					Format:   "percent",
					Value:    0.07,
					Priority: 1,
					Metric:   &fixture.ClientHostCPUMetric,
				},
				{
					ID:       host.MemoryUsage,
					Label:    "Memory",
					Format:   "filesize",
					Value:    0.08,
					Priority: 2,
					Metric:   &fixture.ClientHostMemoryMetric,
				},
				{
					ID:       host.Load1,
					Label:    "Load (1m)",
					Group:    "load",
					Value:    0.09,
					Priority: 11,
					Metric:   &fixture.ClientHostLoad1Metric,
				},
			},
		},
		Controls: []detailed.ControlInstance{},
		Children: []detailed.NodeSummaryGroup{
			{
				Label:      "Pods",
				TopologyID: "pods",
				Columns: []detailed.Column{
					{ID: kubernetes.State, Label: "State"},
					{ID: report.Container, Label: "Containers"},
					{ID: kubernetes.IP, Label: "IP"},
				},
				Nodes: []detailed.NodeSummary{podNodeSummary},
			},
			{
				Label:      "Containers",
				TopologyID: "containers",
				Columns: []detailed.Column{
					{ID: docker.CPUTotalUsage, Label: "CPU"},
					{ID: docker.MemoryUsage, Label: "Memory"},
				},
				Nodes: []detailed.NodeSummary{containerNodeSummary},
			},
			{
				Label:      "Processes",
				TopologyID: "processes",
				Columns: []detailed.Column{
					{ID: process.PID, Label: "PID"},
					{ID: process.CPUUsage, Label: "CPU"},
					{ID: process.MemoryUsage, Label: "Memory"},
				},
				Nodes: []detailed.NodeSummary{process1NodeSummary, process2NodeSummary},
			},
			{
				Label:      "Container Images",
				TopologyID: "containers-by-image",
				Columns: []detailed.Column{
					{ID: report.Container, Label: "# Containers", DefaultSort: true},
				},
				Nodes: []detailed.NodeSummary{containerImageNodeSummary},
			},
		},
		Connections: []detailed.ConnectionsSummary{
			{
				ID:          "incoming-connections",
				TopologyID:  "hosts",
				Label:       "Inbound",
				Columns:     detailed.NormalColumns,
				Connections: []detailed.Connection{},
			},
			{
				ID:         "outgoing-connections",
				TopologyID: "hosts",
				Label:      "Outbound",
				Columns:    detailed.NormalColumns,
				Connections: []detailed.Connection{
					{
						ID:       connectionID(fixture.ServerHostNodeID),
						NodeID:   fixture.ServerHostNodeID,
						Label:    "server",
						Linkable: true,
						Metadata: []report.MetadataRow{
							{
								ID:       "port",
								Value:    "80",
								Datatype: "number",
							},
							{
								ID:       "count",
								Value:    "2",
								Datatype: "number",
							},
						},
					},
				},
			},
		},
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}

func TestMakeDetailedContainerNode(t *testing.T) {
	id := fixture.ServerContainerNodeID
	renderableNodes := render.ContainerWithImageNameRenderer.Render(fixture.Report, nil)
	renderableNode, ok := renderableNodes[id]
	if !ok {
		t.Fatalf("Node not found: %s", id)
	}
	have := detailed.MakeNode("containers", fixture.Report, renderableNodes, renderableNode)

	serverProcessNodeSummary := child(t, render.ProcessRenderer, fixture.ServerProcessNodeID)
	serverProcessNodeSummary.Linkable = true
	want := detailed.Node{
		NodeSummary: detailed.NodeSummary{
			ID:         id,
			Label:      "server",
			LabelMinor: "server.hostname.com",
			Rank:       fixture.ServerContainerImageName,
			Shape:      "hexagon",
			Linkable:   true,
			Pseudo:     false,
			Metadata: []report.MetadataRow{
				{ID: "docker_container_id", Label: "ID", Value: fixture.ServerContainerID, Priority: 1},
				{ID: "docker_container_state_human", Label: "State", Value: "running", Priority: 2},
				{ID: "docker_image_id", Label: "Image ID", Value: fixture.ServerContainerImageID, Priority: 11},
			},
			Metrics: []report.MetricRow{
				{
					ID:       docker.CPUTotalUsage,
					Label:    "CPU",
					Format:   "percent",
					Value:    0.05,
					Priority: 1,
					Metric:   &fixture.ServerContainerCPUMetric,
				},
				{
					ID:       docker.MemoryUsage,
					Label:    "Memory",
					Format:   "filesize",
					Value:    0.06,
					Priority: 2,
					Metric:   &fixture.ServerContainerMemoryMetric,
				},
			},
			Parents: []detailed.Parent{
				{
					ID:         expected.ServerContainerImageNodeID,
					Label:      fixture.ServerContainerImageName,
					TopologyID: "containers-by-image",
				},
				{
					ID:         fixture.ServerHostNodeID,
					Label:      fixture.ServerHostName,
					TopologyID: "hosts",
				},
				{
					ID:         fixture.ServerPodNodeID,
					Label:      "pong-b",
					TopologyID: "pods",
				},
			},
		},
		Controls: []detailed.ControlInstance{},
		Children: []detailed.NodeSummaryGroup{
			{
				Label:      "Processes",
				TopologyID: "processes",
				Columns: []detailed.Column{
					{ID: process.PID, Label: "PID"},
					{ID: process.CPUUsage, Label: "CPU"},
					{ID: process.MemoryUsage, Label: "Memory"},
				},
				Nodes: []detailed.NodeSummary{serverProcessNodeSummary},
			},
		},
		Connections: []detailed.ConnectionsSummary{
			{
				ID:         "incoming-connections",
				TopologyID: "containers",
				Label:      "Inbound",
				Columns:    detailed.NormalColumns,
				Connections: []detailed.Connection{
					{
						ID:       connectionID(fixture.ClientContainerNodeID),
						NodeID:   fixture.ClientContainerNodeID,
						Label:    "client",
						Linkable: true,
						Metadata: []report.MetadataRow{
							{
								ID:       "port",
								Value:    "80",
								Datatype: "number",
							},
							{
								ID:       "count",
								Value:    "2",
								Datatype: "number",
							},
						},
					},
					{
						ID:       connectionID(render.IncomingInternetID),
						NodeID:   render.IncomingInternetID,
						Label:    render.InboundMajor,
						Linkable: true,
						Metadata: []report.MetadataRow{
							{
								ID:       "port",
								Value:    "80",
								Datatype: "number",
							},
							{
								ID:       "count",
								Value:    "1",
								Datatype: "number",
							},
						},
					},
				},
			},
			{
				ID:          "outgoing-connections",
				TopologyID:  "containers",
				Label:       "Outbound",
				Columns:     detailed.NormalColumns,
				Connections: []detailed.Connection{},
			},
		},
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}

func TestMakeDetailedPodNode(t *testing.T) {
	id := fixture.ServerPodNodeID
	renderableNodes := render.PodRenderer.Render(fixture.Report, nil)
	renderableNode, ok := renderableNodes[id]
	if !ok {
		t.Fatalf("Node not found: %s", id)
	}
	have := detailed.MakeNode("pods", fixture.Report, renderableNodes, renderableNode)

	containerNodeSummary := child(t, render.ContainerWithImageNameRenderer, fixture.ServerContainerNodeID)
	serverProcessNodeSummary := child(t, render.ProcessRenderer, fixture.ServerProcessNodeID)
	serverProcessNodeSummary.Linkable = true // Temporary workaround for: https://github.com/weaveworks/scope/issues/1295
	want := detailed.Node{
		NodeSummary: detailed.NodeSummary{
			ID:         id,
			Label:      "pong-b",
			LabelMinor: "1 container",
			Rank:       "ping/pong-b",
			Shape:      "heptagon",
			Linkable:   true,
			Pseudo:     false,
			Metadata: []report.MetadataRow{
				{ID: "kubernetes_state", Label: "State", Value: "running", Priority: 2},
				{ID: "container", Label: "# Containers", Value: "1", Priority: 4, Datatype: "number"},
				{ID: "kubernetes_namespace", Label: "Namespace", Value: "ping", Priority: 5},
			},
			Parents: []detailed.Parent{
				{
					ID:         fixture.ServerHostNodeID,
					Label:      fixture.ServerHostName,
					TopologyID: "hosts",
				},
				{
					ID:         fixture.ServiceNodeID,
					Label:      fixture.ServiceName,
					TopologyID: "services",
				},
			},
		},
		Controls: []detailed.ControlInstance{},
		Children: []detailed.NodeSummaryGroup{
			{
				Label:      "Containers",
				TopologyID: "containers",
				Columns: []detailed.Column{
					{ID: docker.CPUTotalUsage, Label: "CPU"},
					{ID: docker.MemoryUsage, Label: "Memory"},
				},
				Nodes: []detailed.NodeSummary{containerNodeSummary},
			},
			{
				Label:      "Processes",
				TopologyID: "processes",
				Columns: []detailed.Column{
					{ID: process.PID, Label: "PID"},
					{ID: process.CPUUsage, Label: "CPU"},
					{ID: process.MemoryUsage, Label: "Memory"},
				},
				Nodes: []detailed.NodeSummary{serverProcessNodeSummary},
			},
		},
		Connections: []detailed.ConnectionsSummary{
			{
				ID:         "incoming-connections",
				TopologyID: "pods",
				Label:      "Inbound",
				Columns:    detailed.NormalColumns,
				Connections: []detailed.Connection{
					{
						ID:       connectionID(fixture.ClientPodNodeID),
						NodeID:   fixture.ClientPodNodeID,
						Label:    "pong-a",
						Linkable: true,
						Metadata: []report.MetadataRow{
							{
								ID:       "port",
								Value:    "80",
								Datatype: "number",
							},
							{
								ID:       "count",
								Value:    "2",
								Datatype: "number",
							},
						},
					},
					{
						ID:       connectionID(render.IncomingInternetID),
						NodeID:   render.IncomingInternetID,
						Label:    render.InboundMajor,
						Linkable: true,
						Metadata: []report.MetadataRow{
							{
								ID:       "port",
								Value:    "80",
								Datatype: "number",
							},
							{
								ID:       "count",
								Value:    "1",
								Datatype: "number",
							},
						},
					},
				},
			},
			{
				ID:          "outgoing-connections",
				TopologyID:  "pods",
				Label:       "Outbound",
				Columns:     detailed.NormalColumns,
				Connections: []detailed.Connection{},
			},
		},
	}
	if !reflect.DeepEqual(want, have) {
		t.Errorf("%s", test.Diff(want, have))
	}
}
<|endoftext|>


<commit_before>package common

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"io/ioutil"
	"log"

	"engo.io/engo"
	"engo.io/gl"
	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"
)

var (
	dpi = float64(72)
)

type Font struct {
	URL  string
	Size float64
	BG   color.Color
	FG   color.Color
	TTF  *truetype.Font
}

// Create is for loading fonts from the disk, given a location
func (f *Font) Create() error {
	// Read and parse the font
	ttfBytes, err := ioutil.ReadFile(f.URL)
	if err != nil {
		return err
	}

	ttf, err := freetype.ParseFont(ttfBytes)
	if err != nil {
		return err
	}
	f.TTF = ttf

	return nil
}

// CreatePreloaded is for loading fonts which have already been defined (and loaded) within Preload
func (f *Font) CreatePreloaded() error {
	fontres, err := engo.Files.Resource(f.URL)
	if err != nil {
		return err
	}

	font, ok := fontres.(FontResource)
	if !ok {
		return fmt.Errorf("preloaded font is not of type `*truetype.Font`: %s", f.URL)
	}

	f.TTF = font.Font
	return nil
}

func (f *Font) TextDimensions(text string) (int, int, int) {
	fnt := f.TTF
	size := f.Size
	var (
		totalWidth  = fixed.Int26_6(0)
		totalHeight = fixed.Int26_6(size)
		maxYBearing = fixed.Int26_6(0)
	)
	fupe := fixed.Int26_6(fnt.FUnitsPerEm())
	for _, char := range text {
		idx := fnt.Index(char)
		hm := fnt.HMetric(fupe, idx)
		vm := fnt.VMetric(fupe, idx)
		g := truetype.GlyphBuf{}
		err := g.Load(fnt, fupe, idx, font.HintingNone)
		if err != nil {
			log.Println(err)
			return 0, 0, 0
		}
		totalWidth += hm.AdvanceWidth
		yB := (vm.TopSideBearing * fixed.Int26_6(size)) / fupe
		if yB > maxYBearing {
			maxYBearing = yB
		}
	}

	// Scale to actual pixel size
	totalWidth *= fixed.Int26_6(size)
	totalWidth /= fupe

	return int(totalWidth), int(totalHeight), int(maxYBearing)
}

func (f *Font) RenderNRGBA(text string) *image.NRGBA {
	width, _, yBearing := f.TextDimensions(text)
	font := f.TTF
	size := f.Size

	if size <= 0 {
		panic("Font size cannot be <= 0")
	}

	// Default colors
	if f.FG == nil {
		f.FG = color.NRGBA{0, 0, 0, 0}
	}
	if f.BG == nil {
		f.BG = color.NRGBA{0, 0, 0, 0}
	}

	// Colors
	fg := image.NewUniform(f.FG)
	bg := image.NewUniform(f.BG)

	// Create the font context
	c := freetype.NewContext()

	nrgba := image.NewNRGBA(image.Rect(0, 0, width, yBearing))
	draw.Draw(nrgba, nrgba.Bounds(), bg, image.ZP, draw.Src)

	c.SetDPI(dpi)
	c.SetFont(font)
	c.SetFontSize(size)
	c.SetClip(nrgba.Bounds())
	c.SetDst(nrgba)
	c.SetSrc(fg)

	// Draw the text.
	pt := fixed.P(0, yBearing)
	_, err := c.DrawString(text, pt)
	if err != nil {
		log.Println(err)
		return nil
	}

	return nrgba
}

func (f *Font) Render(text string) Texture {
	nrgba := f.RenderNRGBA(text)

	// Create texture
	imObj := NewImageObject(nrgba)
	return NewTextureSingle(imObj)
}

// generateFontAtlas generates the font atlas for this given font, using the first `c` Unicode characters.
func (f *Font) generateFontAtlas(c int) FontAtlas {
	atlas := FontAtlas{
		XLocation: make([]float32, c),
		YLocation: make([]float32, c),
		Width:     make([]float32, c),
		Height:    make([]float32, c),
	}

	var (
		int26Width  fixed.Int26_6
		int26Height fixed.Int26_6

		totalHeight fixed.Int26_6

		fupe        = fixed.Int26_6(f.TTF.FUnitsPerEm())
		maxYBearing = fixed.Int26_6(0)

		currentX float32
		maxX     float32

		totalString string // TODO; string is immutable, so this is relatively inefficient; see also where we use `totalString += string(char)`
		subString   string

		drawCurY int
	)

	// The "full image"
	nrgba := image.NewNRGBA(image.Rect(0, 0, 1200, 5000)) // way too big; hopefully

	for i := 0; i < c; i++ {
		char := rune(i)
		totalString += string(char)
		subString += string(char)
		atlas.XLocation[char] = currentX
		atlas.YLocation[char] = float32(drawCurY)

		idx := f.TTF.Index(char)
		hm := f.TTF.HMetric(fupe, idx)
		vm := f.TTF.VMetric(fupe, idx)

		g := truetype.GlyphBuf{}
		err := g.Load(f.TTF, fupe, idx, font.HintingNone)
		if err != nil {
			log.Println("Error creating font atlas:", err)
			return atlas
		}

		int26Width += hm.AdvanceWidth

		atlas.Width[char] = float32(g.AdvanceWidth * fixed.Int26_6(f.Size) / fupe)

		currentX = float32(int26Width * fixed.Int26_6(f.Size) / fupe)
		if currentX > maxX {
			maxX = currentX
		}

		yB := vm.TopSideBearing - g.Bounds.Min.Y
		if yB > maxYBearing {
			maxYBearing = yB
		}
		atlas.Height[char] = float32(yB * fixed.Int26_6(f.Size) / fupe)

		if int(int26Width*fixed.Int26_6(f.Size)/fupe) > 1024 {
			// Now let's draw these chars!
			subimg := f.RenderNRGBA(subString)
			// TODO: optimize this, because `f.RenderNRGBA` also allocates a new image, while we could be drawing to this one directly.
			draw.Draw(nrgba, image.Rect(0, drawCurY, subimg.Bounds().Max.X, drawCurY+subimg.Bounds().Max.Y), subimg, image.ZP, draw.Src)
			drawCurY += subimg.Bounds().Max.Y

			int26Height += maxYBearing
			totalHeight += maxYBearing
			maxYBearing = fixed.Int26_6(0)

			subString = ""
			int26Width = 0
			currentX = 0
		}
	}

	// Draw the last line as well
	subimg := f.RenderNRGBA(subString)
	// TODO: optimize this, because `f.RenderNRGBA` also allocates a new image, while we could be drawing to this one directly.
	draw.Draw(nrgba, image.Rect(0, drawCurY, subimg.Bounds().Max.X, drawCurY+subimg.Bounds().Max.Y), subimg, image.ZP, draw.Src)
	drawCurY += subimg.Bounds().Max.Y

	int26Height += maxYBearing
	totalHeight += maxYBearing
	maxYBearing = fixed.Int26_6(0)

	atlas.TotalWidth = maxX
	atlas.TotalHeight = float32(drawCurY)

	// Create texture
	actual := image.NewNRGBA(image.Rect(0, 0, int(atlas.TotalWidth), int(atlas.TotalHeight)))
	draw.Draw(actual, actual.Bounds(), nrgba, image.ZP, draw.Src)

	imObj := NewImageObject(actual)
	atlas.Texture = NewTextureSingle(imObj).id
	return atlas
}

// A FontAtlas is a representation of some of the Font characters, as an image
type FontAtlas struct {
	Texture *gl.Texture
	// XLocation contains the X-coordinate of the starting position of all characters
	XLocation []float32
	// YLocation contains the Y-coordinate of the starting position of all characters
	YLocation []float32
	// Width contains the width in pixels of all the characters, including the spacing between characters
	Width []float32
	// Height contains the height in pixels of all the characters
	Height []float32
	// TotalWidth is the total amount of pixels the `FontAtlas` is wide; useful for determining the `Viewport`,
	// which is relative to this value.
	TotalWidth float32
	// TotalHeight is the total amount of pixels the `FontAtlas` is high; useful for determining the `Viewport`,
	// which is relative to this value.
	TotalHeight float32
}

// Text represents a string drawn onto the screen, as used by the `TextShader`.
type Text struct {
	// Font is the reference to the font you're using to render this. This includes the color, as well as the font size.
	Font *Font
	// Text is the actual text you want to draw. This may include newlines (\n).
	Text string
	// LineSpacing is the amount of additional spacing there is between the lines (when `Text` consists of multiple lines),
	// relative to the `Size` of the `Font`.
	LineSpacing float32
	// LetterSpacing is the amount of additional spacing there is between the characters, relative to the `Size` of
	// the `Font`.
	LetterSpacing float32
	// RightToLeft is an experimental variable used to indicate that subsequent characters come to the left of the
	// previous character.
	RightToLeft bool
}

func (Text) Texture() *gl.Texture                       { return nil }
func (Text) Width() float32                             { return 0 }
func (Text) Height() float32                            { return 0 }
func (Text) View() (float32, float32, float32, float32) { return 0, 0, 1, 1 }
func (Text) Close()                                     {}
<commit_msg>font fixes<commit_after>package common

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"io/ioutil"
	"log"

	"engo.io/engo"
	"engo.io/gl"
	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"
)

var (
	dpi = float64(72)
)

type Font struct {
	URL  string
	Size float64
	BG   color.Color
	FG   color.Color
	TTF  *truetype.Font
}

// Create is for loading fonts from the disk, given a location
func (f *Font) Create() error {
	// Read and parse the font
	ttfBytes, err := ioutil.ReadFile(f.URL)
	if err != nil {
		return err
	}

	ttf, err := freetype.ParseFont(ttfBytes)
	if err != nil {
		return err
	}
	f.TTF = ttf

	return nil
}

// CreatePreloaded is for loading fonts which have already been defined (and loaded) within Preload
func (f *Font) CreatePreloaded() error {
	fontres, err := engo.Files.Resource(f.URL)
	if err != nil {
		return err
	}

	font, ok := fontres.(FontResource)
	if !ok {
		return fmt.Errorf("preloaded font is not of type `*truetype.Font`: %s", f.URL)
	}

	f.TTF = font.Font
	return nil
}

func (f *Font) TextDimensions(text string) (int, int, int) {
	fnt := f.TTF
	size := f.Size
	var (
		totalWidth  = fixed.Int26_6(0)
		totalHeight = fixed.Int26_6(size)
		maxYBearing = fixed.Int26_6(0)
	)
	fupe := fixed.Int26_6(fnt.FUnitsPerEm())

	for _, char := range text {
		idx := fnt.Index(char)
		hm := fnt.HMetric(fupe, idx)
		vm := fnt.VMetric(fupe, idx)
		g := truetype.GlyphBuf{}
		err := g.Load(fnt, fupe, idx, font.HintingNone)
		if err != nil {
			log.Println(err)
			return 0, 0, 0
		}
		totalWidth += hm.AdvanceWidth

		yB := (vm.TopSideBearing * fixed.Int26_6(size)) / fupe
		if yB > maxYBearing {
			maxYBearing = yB
		}
		dY := (vm.AdvanceHeight * fixed.Int26_6(size)) / fupe
		if dY > totalHeight {
			totalHeight = dY
		}
	}

	// Scale to actual pixel size
	totalWidth *= fixed.Int26_6(size)
	totalWidth /= fupe

	return int(totalWidth), int(totalHeight), int(maxYBearing)
}

func (f *Font) RenderNRGBA(text string) *image.NRGBA {
	width, height, yBearing := f.TextDimensions(text)
	font := f.TTF
	size := f.Size

	if size <= 0 {
		panic("Font size cannot be <= 0")
	}

	// Default colors
	if f.FG == nil {
		f.FG = color.NRGBA{0, 0, 0, 0}
	}
	if f.BG == nil {
		f.BG = color.NRGBA{0, 0, 0, 0}
	}

	// Colors
	fg := image.NewUniform(f.FG)
	bg := image.NewUniform(f.BG)

	// Create the font context
	c := freetype.NewContext()

	nrgba := image.NewNRGBA(image.Rect(0, 0, width, height))
	draw.Draw(nrgba, nrgba.Bounds(), bg, image.ZP, draw.Src)

	c.SetDPI(dpi)
	c.SetFont(font)
	c.SetFontSize(size)
	c.SetClip(nrgba.Bounds())
	c.SetDst(nrgba)
	c.SetSrc(fg)

	// Draw the text.
	pt := fixed.P(0, yBearing)
	_, err := c.DrawString(text, pt)
	if err != nil {
		log.Println(err)
		return nil
	}

	return nrgba
}

func (f *Font) Render(text string) Texture {
	nrgba := f.RenderNRGBA(text)

	// Create texture
	imObj := NewImageObject(nrgba)
	return NewTextureSingle(imObj)
}

// generateFontAtlas generates the font atlas for this given font, using the first `c` Unicode characters.
func (f *Font) generateFontAtlas(c int) FontAtlas {
	atlas := FontAtlas{
		XLocation: make([]float32, c),
		YLocation: make([]float32, c),
		Width:     make([]float32, c),
		Height:    make([]float32, c),
	}

	var (
		int26Width  fixed.Int26_6
		int26Height fixed.Int26_6

		totalHeight fixed.Int26_6

		fupe        = fixed.Int26_6(f.TTF.FUnitsPerEm())
		maxYBearing = fixed.Int26_6(0)

		currentX float32
		maxX     float32

		totalString string // TODO; string is immutable, so this is relatively inefficient; see also where we use `totalString += string(char)`
		subString   string

		drawCurY int
	)

	// The "full image"
	nrgba := image.NewNRGBA(image.Rect(0, 0, 1200, 5000)) // way too big; hopefully

	for i := 0; i < c; i++ {
		char := rune(i)
		totalString += string(char)
		subString += string(char)
		atlas.XLocation[char] = currentX
		atlas.YLocation[char] = float32(drawCurY)

		idx := f.TTF.Index(char)
		hm := f.TTF.HMetric(fupe, idx)
		vm := f.TTF.VMetric(fupe, idx)

		g := truetype.GlyphBuf{}
		err := g.Load(f.TTF, fupe, idx, font.HintingNone)
		if err != nil {
			log.Println("Error creating font atlas:", err)
			return atlas
		}

		int26Width += hm.AdvanceWidth

		atlas.Width[char] = float32((g.AdvanceWidth + hm.LeftSideBearing) * fixed.Int26_6(f.Size) / fupe)

		currentX = float32((int26Width) * fixed.Int26_6(f.Size) / fupe)
		if currentX > maxX {
			maxX = currentX
		}

		yB := vm.TopSideBearing - g.Bounds.Min.Y
		if yB > maxYBearing {
			maxYBearing = yB
		}
		atlas.Height[char] = float32(yB * fixed.Int26_6(f.Size) / fupe)

		if int(int26Width*fixed.Int26_6(f.Size)/fupe) > 1024 {
			// Now let's draw these chars!
			subimg := f.RenderNRGBA(subString)
			// TODO: optimize this, because `f.RenderNRGBA` also allocates a new image, while we could be drawing to this one directly.
			draw.Draw(nrgba, image.Rect(0, drawCurY, subimg.Bounds().Max.X, drawCurY+subimg.Bounds().Max.Y), subimg, image.ZP, draw.Src)
			drawCurY += subimg.Bounds().Max.Y

			int26Height += maxYBearing
			totalHeight += maxYBearing
			maxYBearing = fixed.Int26_6(0)

			subString = ""
			int26Width = 0
			currentX = 0
		}
	}

	// Draw the last line as well
	subimg := f.RenderNRGBA(subString)
	// TODO: optimize this, because `f.RenderNRGBA` also allocates a new image, while we could be drawing to this one directly.
	draw.Draw(nrgba, image.Rect(0, drawCurY, subimg.Bounds().Max.X, drawCurY+subimg.Bounds().Max.Y), subimg, image.ZP, draw.Src)
	drawCurY += subimg.Bounds().Max.Y

	int26Height += maxYBearing
	totalHeight += maxYBearing
	maxYBearing = fixed.Int26_6(0)

	atlas.TotalWidth = maxX
	atlas.TotalHeight = float32(drawCurY)

	// Create texture
	actual := image.NewNRGBA(image.Rect(0, 0, int(atlas.TotalWidth), int(atlas.TotalHeight)))
	draw.Draw(actual, actual.Bounds(), nrgba, image.ZP, draw.Src)

	imObj := NewImageObject(actual)
	atlas.Texture = NewTextureSingle(imObj).id
	return atlas
}

// A FontAtlas is a representation of some of the Font characters, as an image
type FontAtlas struct {
	Texture *gl.Texture
	// XLocation contains the X-coordinate of the starting position of all characters
	XLocation []float32
	// YLocation contains the Y-coordinate of the starting position of all characters
	YLocation []float32
	// Width contains the width in pixels of all the characters, including the spacing between characters
	Width []float32
	// Height contains the height in pixels of all the characters
	Height []float32
	// TotalWidth is the total amount of pixels the `FontAtlas` is wide; useful for determining the `Viewport`,
	// which is relative to this value.
	TotalWidth float32
	// TotalHeight is the total amount of pixels the `FontAtlas` is high; useful for determining the `Viewport`,
	// which is relative to this value.
	TotalHeight float32
}

// Text represents a string drawn onto the screen, as used by the `TextShader`.
type Text struct {
	// Font is the reference to the font you're using to render this. This includes the color, as well as the font size.
	Font *Font
	// Text is the actual text you want to draw. This may include newlines (\n).
	Text string
	// LineSpacing is the amount of additional spacing there is between the lines (when `Text` consists of multiple lines),
	// relative to the `Size` of the `Font`.
	LineSpacing float32
	// LetterSpacing is the amount of additional spacing there is between the characters, relative to the `Size` of
	// the `Font`.
	LetterSpacing float32
	// RightToLeft is an experimental variable used to indicate that subsequent characters come to the left of the
	// previous character.
	RightToLeft bool
}

func (Text) Texture() *gl.Texture                       { return nil }
func (Text) Width() float32                             { return 0 }
func (Text) Height() float32                            { return 0 }
func (Text) View() (float32, float32, float32, float32) { return 0, 0, 1, 1 }
func (Text) Close()                                     {}
<|endoftext|>


<commit_before>package common

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"io/ioutil"
	"log"

	"engo.io/engo"
	"engo.io/gl"
	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"
)

var (
	dpi = float64(72)
)

type Font struct {
	URL  string
	Size float64
	BG   color.Color
	FG   color.Color
	TTF  *truetype.Font
}

// Create is for loading fonts from the disk, given a location
func (f *Font) Create() error {
	// Read and parse the font
	ttfBytes, err := ioutil.ReadFile(f.URL)
	if err != nil {
		return err
	}

	ttf, err := freetype.ParseFont(ttfBytes)
	if err != nil {
		return err
	}
	f.TTF = ttf

	return nil
}

// CreatePreloaded is for loading fonts which have already been defined (and loaded) within Preload
func (f *Font) CreatePreloaded() error {
	fontres, err := engo.Files.Resource(f.URL)
	if err != nil {
		return err
	}

	font, ok := fontres.(FontResource)
	if !ok {
		return fmt.Errorf("preloaded font is not of type `*truetype.Font`: %s", f.URL)
	}

	f.TTF = font.Font
	return nil
}

func (f *Font) TextDimensions(text string) (int, int, int) {
	fnt := f.TTF
	size := f.Size
	var (
		totalWidth  = fixed.Int26_6(0)
		totalHeight = fixed.Int26_6(size)
		maxYBearing = fixed.Int26_6(0)
	)
	fupe := fixed.Int26_6(fnt.FUnitsPerEm())

	for _, char := range text {
		idx := fnt.Index(char)
		hm := fnt.HMetric(fupe, idx)
		vm := fnt.VMetric(fupe, idx)
		g := truetype.GlyphBuf{}
		err := g.Load(fnt, fupe, idx, font.HintingNone)
		if err != nil {
			log.Println(err)
			return 0, 0, 0
		}
		totalWidth += hm.AdvanceWidth

		yB := (vm.TopSideBearing * fixed.Int26_6(size)) / fupe
		if yB > maxYBearing {
			maxYBearing = yB
		}
		dY := (vm.AdvanceHeight * fixed.Int26_6(size)) / fupe
		if dY > totalHeight {
			totalHeight = dY
		}
	}

	// Scale to actual pixel size
	totalWidth *= fixed.Int26_6(size)
	totalWidth /= fupe

	return int(totalWidth), int(totalHeight), int(maxYBearing)
}

func (f *Font) RenderNRGBA(text string) *image.NRGBA {
	width, height, yBearing := f.TextDimensions(text)
	font := f.TTF
	size := f.Size

	if size <= 0 {
		panic("Font size cannot be <= 0")
	}

	// Default colors
	if f.FG == nil {
		f.FG = color.NRGBA{0, 0, 0, 0}
	}
	if f.BG == nil {
		f.BG = color.NRGBA{0, 0, 0, 0}
	}

	// Colors
	fg := image.NewUniform(f.FG)
	bg := image.NewUniform(f.BG)

	// Create the font context
	c := freetype.NewContext()

	nrgba := image.NewNRGBA(image.Rect(0, 0, width, height))
	draw.Draw(nrgba, nrgba.Bounds(), bg, image.ZP, draw.Src)

	c.SetDPI(dpi)
	c.SetFont(font)
	c.SetFontSize(size)
	c.SetClip(nrgba.Bounds())
	c.SetDst(nrgba)
	c.SetSrc(fg)

	// Draw the text.
	pt := fixed.P(0, yBearing)
	_, err := c.DrawString(text, pt)
	if err != nil {
		log.Println(err)
		return nil
	}

	return nrgba
}

func (f *Font) Render(text string) Texture {
	nrgba := f.RenderNRGBA(text)

	// Create texture
	imObj := NewImageObject(nrgba)
	return NewTextureSingle(imObj)
}

// generateFontAtlas generates the font atlas for this given font, using the first `c` Unicode characters.
func (f *Font) generateFontAtlas(c int) FontAtlas {
	atlas := FontAtlas{
		XLocation: make([]float32, c),
		YLocation: make([]float32, c),
		Width:     make([]float32, c),
		Height:    make([]float32, c),
	}

	var (
		int26Width  fixed.Int26_6
		int26Height fixed.Int26_6

		totalHeight fixed.Int26_6

		fupe        = fixed.Int26_6(f.TTF.FUnitsPerEm())
		maxYBearing = fixed.Int26_6(0)

		currentX float32
		maxX     float32

		totalString string // TODO; string is immutable, so this is relatively inefficient; see also where we use `totalString += string(char)`
		subString   string

		drawCurY int
	)

	// The "full image"
	nrgba := image.NewNRGBA(image.Rect(0, 0, 1200, 5000)) // way too big; hopefully

	for i := 0; i < c; i++ {
		char := rune(i)
		totalString += string(char)
		subString += string(char)
		atlas.XLocation[char] = currentX
		atlas.YLocation[char] = float32(drawCurY)

		idx := f.TTF.Index(char)
		hm := f.TTF.HMetric(fupe, idx)
		vm := f.TTF.VMetric(fupe, idx)

		g := truetype.GlyphBuf{}
		err := g.Load(f.TTF, fupe, idx, font.HintingNone)
		if err != nil {
			log.Println("Error creating font atlas:", err)
			return atlas
		}

		int26Width += hm.AdvanceWidth

		atlas.Width[char] = float32((g.AdvanceWidth + hm.LeftSideBearing) * fixed.Int26_6(f.Size) / fupe)

		currentX = float32((int26Width) * fixed.Int26_6(f.Size) / fupe)
		if currentX > maxX {
			maxX = currentX
		}

		yB := vm.TopSideBearing - g.Bounds.Min.Y
		if yB > maxYBearing {
			maxYBearing = yB
		}
		atlas.Height[char] = float32(yB * fixed.Int26_6(f.Size) / fupe)

		if int(int26Width*fixed.Int26_6(f.Size)/fupe) > 1024 {
			// Now let's draw these chars!
			subimg := f.RenderNRGBA(subString)
			// TODO: optimize this, because `f.RenderNRGBA` also allocates a new image, while we could be drawing to this one directly.
			draw.Draw(nrgba, image.Rect(0, drawCurY, subimg.Bounds().Max.X, drawCurY+subimg.Bounds().Max.Y), subimg, image.ZP, draw.Src)
			drawCurY += subimg.Bounds().Max.Y

			int26Height += maxYBearing
			totalHeight += maxYBearing
			maxYBearing = fixed.Int26_6(0)

			subString = ""
			int26Width = 0
			currentX = 0
		}
	}

	// Draw the last line as well
	subimg := f.RenderNRGBA(subString)
	// TODO: optimize this, because `f.RenderNRGBA` also allocates a new image, while we could be drawing to this one directly.
	draw.Draw(nrgba, image.Rect(0, drawCurY, subimg.Bounds().Max.X, drawCurY+subimg.Bounds().Max.Y), subimg, image.ZP, draw.Src)
	drawCurY += subimg.Bounds().Max.Y

	int26Height += maxYBearing
	totalHeight += maxYBearing
	maxYBearing = fixed.Int26_6(0)

	atlas.TotalWidth = maxX
	atlas.TotalHeight = float32(drawCurY)

	// Create texture
	actual := image.NewNRGBA(image.Rect(0, 0, int(atlas.TotalWidth), int(atlas.TotalHeight)))
	draw.Draw(actual, actual.Bounds(), nrgba, image.ZP, draw.Src)

	imObj := NewImageObject(actual)
	atlas.Texture = NewTextureSingle(imObj).id
	return atlas
}

// A FontAtlas is a representation of some of the Font characters, as an image
type FontAtlas struct {
	Texture *gl.Texture
	// XLocation contains the X-coordinate of the starting position of all characters
	XLocation []float32
	// YLocation contains the Y-coordinate of the starting position of all characters
	YLocation []float32
	// Width contains the width in pixels of all the characters, including the spacing between characters
	Width []float32
	// Height contains the height in pixels of all the characters
	Height []float32
	// TotalWidth is the total amount of pixels the `FontAtlas` is wide; useful for determining the `Viewport`,
	// which is relative to this value.
	TotalWidth float32
	// TotalHeight is the total amount of pixels the `FontAtlas` is high; useful for determining the `Viewport`,
	// which is relative to this value.
	TotalHeight float32
}

// Text represents a string drawn onto the screen, as used by the `TextShader`.
type Text struct {
	// Font is the reference to the font you're using to render this. This includes the color, as well as the font size.
	Font *Font
	// Text is the actual text you want to draw. This may include newlines (\n).
	Text string
	// LineSpacing is the amount of additional spacing there is between the lines (when `Text` consists of multiple lines),
	// relative to the `Size` of the `Font`.
	LineSpacing float32
	// LetterSpacing is the amount of additional spacing there is between the characters, relative to the `Size` of
	// the `Font`.
	LetterSpacing float32
	// RightToLeft is an experimental variable used to indicate that subsequent characters come to the left of the
	// previous character.
	RightToLeft bool
}

func (Text) Texture() *gl.Texture                       { return nil }
func (Text) Width() float32                             { return 0 }
func (Text) Height() float32                            { return 0 }
func (Text) View() (float32, float32, float32, float32) { return 0, 0, 1, 1 }
func (Text) Close()                                     {}
<commit_msg>font atlas<commit_after>package common

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"io/ioutil"
	"log"

	"engo.io/engo"
	"engo.io/gl"
	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"
	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"
)

var (
	dpi = float64(72)
)

type Font struct {
	URL  string
	Size float64
	BG   color.Color
	FG   color.Color
	TTF  *truetype.Font
}

// Create is for loading fonts from the disk, given a location
func (f *Font) Create() error {
	// Read and parse the font
	ttfBytes, err := ioutil.ReadFile(f.URL)
	if err != nil {
		return err
	}

	ttf, err := freetype.ParseFont(ttfBytes)
	if err != nil {
		return err
	}
	f.TTF = ttf

	return nil
}

// CreatePreloaded is for loading fonts which have already been defined (and loaded) within Preload
func (f *Font) CreatePreloaded() error {
	fontres, err := engo.Files.Resource(f.URL)
	if err != nil {
		return err
	}

	font, ok := fontres.(FontResource)
	if !ok {
		return fmt.Errorf("preloaded font is not of type `*truetype.Font`: %s", f.URL)
	}

	f.TTF = font.Font
	return nil
}

func (f *Font) TextDimensions(text string) (int, int, int) {
	fnt := f.TTF
	size := f.Size
	var (
		totalWidth  = fixed.Int26_6(0)
		totalHeight = fixed.Int26_6(size)
		maxYBearing = fixed.Int26_6(0)
	)
	fupe := fixed.Int26_6(fnt.FUnitsPerEm())

	for _, char := range text {
		idx := fnt.Index(char)
		hm := fnt.HMetric(fupe, idx)
		vm := fnt.VMetric(fupe, idx)
		g := truetype.GlyphBuf{}
		err := g.Load(fnt, fupe, idx, font.HintingNone)
		if err != nil {
			log.Println(err)
			return 0, 0, 0
		}
		totalWidth += hm.AdvanceWidth

		yB := (vm.TopSideBearing * fixed.Int26_6(size)) / fupe
		if yB > maxYBearing {
			maxYBearing = yB
		}
		dY := (vm.AdvanceHeight * fixed.Int26_6(size)) / fupe
		if dY > totalHeight {
			totalHeight = dY
		}
	}

	// Scale to actual pixel size
	totalWidth *= fixed.Int26_6(size)
	totalWidth /= fupe

	return int(totalWidth), int(totalHeight), int(maxYBearing)
}

func (f *Font) RenderNRGBA(text string) *image.NRGBA {
	width, height, yBearing := f.TextDimensions(text)
	font := f.TTF
	size := f.Size

	if size <= 0 {
		panic("Font size cannot be <= 0")
	}

	// Default colors
	if f.FG == nil {
		f.FG = color.NRGBA{0, 0, 0, 0}
	}
	if f.BG == nil {
		f.BG = color.NRGBA{0, 0, 0, 0}
	}

	// Colors
	fg := image.NewUniform(f.FG)
	bg := image.NewUniform(f.BG)

	// Create the font context
	c := freetype.NewContext()

	nrgba := image.NewNRGBA(image.Rect(0, 0, width, height))
	draw.Draw(nrgba, nrgba.Bounds(), bg, image.ZP, draw.Src)

	c.SetDPI(dpi)
	c.SetFont(font)
	c.SetFontSize(size)
	c.SetClip(nrgba.Bounds())
	c.SetDst(nrgba)
	c.SetSrc(fg)

	// Draw the text.
	pt := fixed.P(0, yBearing)
	_, err := c.DrawString(text, pt)
	if err != nil {
		log.Println(err)
		return nil
	}

	return nrgba
}

func (f *Font) Render(text string) Texture {
	nrgba := f.RenderNRGBA(text)

	// Create texture
	imObj := NewImageObject(nrgba)
	return NewTextureSingle(imObj)
}

// generateFontAtlas generates the font atlas for this given font, using the first `c` Unicode characters.
func (f *Font) generateFontAtlas(c int) FontAtlas {
	atlas := FontAtlas{
		XLocation: make([]float32, c),
		YLocation: make([]float32, c),
		Width:     make([]float32, c),
		Height:    make([]float32, c),
	}

	currentX := float32(0)
	currentY := float32(0)

	// Default colors
	if f.FG == nil {
		f.FG = color.NRGBA{0, 0, 0, 0}
	}
	if f.BG == nil {
		f.BG = color.NRGBA{0, 0, 0, 0}
	}

	d := &font.Drawer{}
	d.Src = image.NewUniform(f.FG)
	d.Face = truetype.NewFace(f.TTF, &truetype.Options{
		Size:    f.Size,
		DPI:     dpi,
		Hinting: font.HintingNone,
	})

	lineHeight := d.Face.Metrics().Height

	for i := 0; i < c; i++ {
		_, adv, ok := d.Face.GlyphBounds(rune(i))
		if !ok {
			continue
		}

		atlas.Width[i] = float32(adv.Ceil())
		atlas.Height[i] = float32(lineHeight.Ceil()) + 5
		atlas.XLocation[i] = currentX
		atlas.YLocation[i] = currentY

		currentX += float32(adv.Ceil())
		if currentX > 255 || i >= c-1 {
			if currentX > atlas.TotalWidth {
				atlas.TotalWidth = currentX
			}
			currentX = 0
			currentY += float32(lineHeight.Ceil()) + 5
			atlas.TotalHeight += float32(lineHeight.Ceil()) + 5
		}
	}

	// Create texture
	actual := image.NewNRGBA(image.Rect(0, 0, int(atlas.TotalWidth), int(atlas.TotalHeight)))
	d.Dst = actual

	currentX = 0
	currentY = float32(lineHeight.Ceil())
	for i := 0; i < c; i++ {
		_, adv, ok := d.Face.GlyphBounds(rune(i))
		if !ok {
			continue
		}
		d.Dot = fixed.P(int(currentX), int(currentY))
		d.DrawBytes([]byte{byte(i)})
		currentX += float32(adv.Ceil())
		if currentX > 255 || i == c-1 {
			currentX = 0
			currentY += float32(lineHeight.Ceil()) + 5
		}
	}

	imObj := NewImageObject(actual)
	atlas.Texture = NewTextureSingle(imObj).id

	return atlas
}

// A FontAtlas is a representation of some of the Font characters, as an image
type FontAtlas struct {
	Texture *gl.Texture
	// XLocation contains the X-coordinate of the starting position of all characters
	XLocation []float32
	// YLocation contains the Y-coordinate of the starting position of all characters
	YLocation []float32
	// Width contains the width in pixels of all the characters, including the spacing between characters
	Width []float32
	// Height contains the height in pixels of all the characters
	Height []float32
	// TotalWidth is the total amount of pixels the `FontAtlas` is wide; useful for determining the `Viewport`,
	// which is relative to this value.
	TotalWidth float32
	// TotalHeight is the total amount of pixels the `FontAtlas` is high; useful for determining the `Viewport`,
	// which is relative to this value.
	TotalHeight float32
}

// Text represents a string drawn onto the screen, as used by the `TextShader`.
type Text struct {
	// Font is the reference to the font you're using to render this. This includes the color, as well as the font size.
	Font *Font
	// Text is the actual text you want to draw. This may include newlines (\n).
	Text string
	// LineSpacing is the amount of additional spacing there is between the lines (when `Text` consists of multiple lines),
	// relative to the `Size` of the `Font`.
	LineSpacing float32
	// LetterSpacing is the amount of additional spacing there is between the characters, relative to the `Size` of
	// the `Font`.
	LetterSpacing float32
	// RightToLeft is an experimental variable used to indicate that subsequent characters come to the left of the
	// previous character.
	RightToLeft bool
}

func (Text) Texture() *gl.Texture                       { return nil }
func (Text) Width() float32                             { return 0 }
func (Text) Height() float32                            { return 0 }
func (Text) View() (float32, float32, float32, float32) { return 0, 0, 1, 1 }
func (Text) Close()                                     {}
<|endoftext|>


<commit_before>package boleto

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"html/template"

	"github.com/boombuler/barcode"
	"github.com/boombuler/barcode/twooffive"

	"image/jpeg"

	"bitbucket.org/mundipagg/boletoapi/models"
	"bitbucket.org/mundipagg/boletoapi/tmpl"
	gin "gopkg.in/gin-gonic/gin.v1"
)

const templateBoleto = `

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    {{template "boletoForm" .}}

	{{template "boletoForm" .}}
	<input class="no-print" type="button" onclick="window.print()" value="Imprimir">

`

const boletoForm = `
{{define "boletoForm"}}

                    {{.BankLogo}}

                {{.BankNumber}}
                {{fmtDigitableLine .DigitableLine}}

                    Local de Pagamento

                    ATÉ O VENCIMENTO EM QUALQUER BANCO OU CORRESPONDENTE NÃO BANCÁRIO, APÓS O VENCIMENTO, PAGUE EM QUALQUER BANCO OU CORRESPONDENTE NÃO BANCÁRIO

                    Data de Vencimento

{{.Boleto.Title.ExpireDateTime | brdate}}

                    Nome do Beneficiário / CNPJ / CPF / Endereço:

                            {{.Boleto.Recipient.Name}}
                            CNPJ {{fmtDoc .Boleto.Recipient.Document}}

                    {{.Boleto.Recipient.Address.Street}},
					{{.Boleto.Recipient.Address.Number}} -
					{{.Boleto.Recipient.Address.Complement}} -
					{{.Boleto.Recipient.Address.District}} -
					{{.Boleto.Recipient.Address.City}} -
					{{.Boleto.Recipient.Address.StateCode}} -
					{{.Boleto.Recipient.Address.ZipCode}}

                    Agência/Código Beneficiário

{{.Boleto.Agreement.Agency}}/{{.Boleto.Agreement.Account}}-{{.Boleto.Agreement.AccountDigit}}

                    Data do Documento

{{today | brdate}}

                    Num. do Documento

1

                    Espécie doc

DM

                    Aceite

N

                    Data Processamento

{{today | brdate}}

                    Carteira/Nosso Número

{{.Boleto.Agreement.Wallet}}/{{.Boleto.Title.OurNumber}}

                    Uso do Banco

                    Carteira

{{.Boleto.Agreement.Wallet}}

                    Espécie

R$

                    Quantidade

N

                    Valor

{{fmtNumber .Boleto.Title.AmountInCents}}

                    (=) Valor do Documento

{{fmtNumber .Boleto.Title.AmountInCents}}

                    Instruções de responsabilidade do BENEFICIÁRIO. Qualquer dúvida sobre este boleto contate o beneficiário.

                    (-) Descontos/Abatimento

                    (+) Juros/Multa

                    (=) Valor Pago

                            Nome do Pagador:  {{.Boleto.Buyer.Name}}
                            CNPJ/CPF:  {{fmtDoc .Boleto.Buyer.Document}}

                            Endereço:  {{.Boleto.Buyer.Address.Street}} {{.Boleto.Buyer.Address.Number}} - {{.Boleto.Buyer.Address.District}} - {{.Boleto.Buyer.Address.City}} - {{.Boleto.Buyer.Address.StateCode}} - {{.Boleto.Buyer.Address.ZipCode}}

                            Sacador/Avalista:
                            CNPJ/CPF:

	{{end}}
`

func HTML(w gin.ResponseWriter, boleto models.BoletoView) {
	b := tmpl.New()
	boleto.BankLogo = template.HTML(logoBB)
	bcode, _ := twooffive.Encode(boleto.Barcode, true)
	orgBounds := bcode.Bounds()
	orgWidth := orgBounds.Max.X - orgBounds.Min.X
	img, _ := barcode.Scale(bcode, orgWidth, 50)
	buf := new(bytes.Buffer)
	err := jpeg.Encode(buf, img, nil)
	boleto.Barcode64 = base64.StdEncoding.EncodeToString(buf.Bytes())
	s, err := b.From(boleto).To(templateBoleto).Transform(boletoForm)
	if err != nil {
		fmt.Println(err)
	}
	w.WriteString(s)
}
<commit_msg>:construction: Deixa carteira 17 chumbada no layout do boleto porque não vamos trabalhar com outra carteira no BB<commit_after>package boleto

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"html/template"

	"github.com/boombuler/barcode"
	"github.com/boombuler/barcode/twooffive"

	"image/jpeg"

	"bitbucket.org/mundipagg/boletoapi/models"
	"bitbucket.org/mundipagg/boletoapi/tmpl"
	gin "gopkg.in/gin-gonic/gin.v1"
)

const templateBoleto = `

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    {{template "boletoForm" .}}

	{{template "boletoForm" .}}
	<input class="no-print" type="button" onclick="window.print()" value="Imprimir">

`

const boletoForm = `
{{define "boletoForm"}}

                    {{.BankLogo}}

                {{.BankNumber}}
                {{fmtDigitableLine .DigitableLine}}

                    Local de Pagamento

                    ATÉ O VENCIMENTO EM QUALQUER BANCO OU CORRESPONDENTE NÃO BANCÁRIO, APÓS O VENCIMENTO, PAGUE EM QUALQUER BANCO OU CORRESPONDENTE NÃO BANCÁRIO

                    Data de Vencimento

{{.Boleto.Title.ExpireDateTime | brdate}}

                    Nome do Beneficiário / CNPJ / CPF / Endereço:

                            {{.Boleto.Recipient.Name}}
                            CNPJ {{fmtDoc .Boleto.Recipient.Document}}

                    {{.Boleto.Recipient.Address.Street}},
					{{.Boleto.Recipient.Address.Number}} -
					{{.Boleto.Recipient.Address.Complement}} -
					{{.Boleto.Recipient.Address.District}} -
					{{.Boleto.Recipient.Address.City}} -
					{{.Boleto.Recipient.Address.StateCode}} -
					{{.Boleto.Recipient.Address.ZipCode}}

                    Agência/Código Beneficiário

{{.Boleto.Agreement.Agency}}/{{.Boleto.Agreement.Account}}-{{.Boleto.Agreement.AccountDigit}}

                    Data do Documento

{{today | brdate}}

                    Num. do Documento

1

                    Espécie doc

DM

                    Aceite

N

                    Data Processamento

{{today | brdate}}

                    Carteira/Nosso Número

17/{{.Boleto.Title.OurNumber}}

                    Uso do Banco

                    Carteira

17

                    Espécie

R$

                    Quantidade

N

                    Valor

{{fmtNumber .Boleto.Title.AmountInCents}}

                    (=) Valor do Documento

{{fmtNumber .Boleto.Title.AmountInCents}}

                    Instruções de responsabilidade do BENEFICIÁRIO. Qualquer dúvida sobre este boleto contate o beneficiário.

                    (-) Descontos/Abatimento

                    (+) Juros/Multa

                    (=) Valor Pago

                            Nome do Pagador:  {{.Boleto.Buyer.Name}}
                            CNPJ/CPF:  {{fmtDoc .Boleto.Buyer.Document}}

                            Endereço:  {{.Boleto.Buyer.Address.Street}} {{.Boleto.Buyer.Address.Number}} - {{.Boleto.Buyer.Address.District}} - {{.Boleto.Buyer.Address.City}} - {{.Boleto.Buyer.Address.StateCode}} - {{.Boleto.Buyer.Address.ZipCode}}

                            Sacador/Avalista:
                            CNPJ/CPF:

	{{end}}
`

func HTML(w gin.ResponseWriter, boleto models.BoletoView) {
	b := tmpl.New()
	boleto.BankLogo = template.HTML(logoBB)
	bcode, _ := twooffive.Encode(boleto.Barcode, true)
	orgBounds := bcode.Bounds()
	orgWidth := orgBounds.Max.X - orgBounds.Min.X
	img, _ := barcode.Scale(bcode, orgWidth, 50)
	buf := new(bytes.Buffer)
	err := jpeg.Encode(buf, img, nil)
	boleto.Barcode64 = base64.StdEncoding.EncodeToString(buf.Bytes())
	s, err := b.From(boleto).To(templateBoleto).Transform(boletoForm)
	if err != nil {
		fmt.Println(err)
	}
	w.WriteString(s)
}
<|endoftext|>