<commit_before>package db

import (
	"database/sql"
	"encoding/json"
	"github.com/StefanKjartansson/eventhub"
	_ "github.com/lib/pq"
	"log"
	"strings"
)

const (
	insertSQL = `
    INSERT INTO "event"
        (
            "key",
            "created",
            "updated",
            "payload",
            "description",
            "importance",
            "origin",
            "entities",
            "other_references",
            "actors",
            "tags"
        )
    VALUES
        (
            $1,
            now(),
            now(),
            $2,
            $3,
            $4,
            $5,
            ARRAY[$6],
            ARRAY[$7],
            ARRAY[$8],
            ARRAY[$9]
        )
    RETURNING
        "id",
        "created",
        "updated";
    `

	updateSQL = `
    UPDATE "event"
    SET
        "key" = $1,
        "payload" = $2,
        "description" = $3,
        "importance" = $4,
        "origin" = $5,
        "entities" = ARRAY[$6],
        "other_references" = ARRAY[$7],
        "actors" = ARRAY[$8],
        "tags" = ARRAY[$9],
        "updated" = now()
    WHERE
        "id" = $10
    RETURNING
        "updated";
    `

	selectByIdSQL = `
    SELECT
        "id",
        "key",
        "created",
        "updated",
        "payload",
        "description",
        "importance",
        "origin",
        "entities",
        "other_references",
        "actors",
        "tags"
    FROM
        "event"
    WHERE "id" = $1
    `
)

type PostgresDataSource struct {
	pg         *sql.DB
	insert     *sql.Stmt
	update     *sql.Stmt
	selectbyid *sql.Stmt
}

func (p *PostgresDataSource) GetById(id int) (*eventhub.Event, error) {

	var e eventhub.Event
	var err error
	var tx *sql.Tx

	if tx, err = p.pg.Begin(); err != nil {
		return nil, err
	}

	defer func() {
		if err != nil {
			tx.Rollback()
			log.Fatal(err)
		} else {
			tx.Commit()
		}
	}()

	var entities StringSlice
	var references StringSlice
	var actors StringSlice
	var tags StringSlice

	temp := []byte{}

	err = tx.Stmt(p.selectbyid).QueryRow(id).Scan(
		&e.ID,
		&e.Key,
		&e.Created,
		&e.Updated,
		&temp,
		&e.Description,
		&e.Importance,
		&e.Origin,
		&entities,
		&references,
		&actors,
		&tags)

	if err != nil {
		return nil, err
	}

	var data interface{}
	err = json.Unmarshal(temp, &data)

	if err != nil {
		return nil, err
	}

	e.Payload = data
	e.Entities = entities
	e.OtherReferences = references
	e.Actors = actors
	e.Tags = tags

	return &e, nil

}

func (p *PostgresDataSource) Save(e *eventhub.Event) (err error) {

	var tx *sql.Tx

	if tx, err = p.pg.Begin(); err != nil {
		return
	}

	defer func() {
		if err != nil {
			tx.Rollback()
			log.Fatal(err)
		} else {
			tx.Commit()
		}
	}()

	b, err := json.Marshal(e.Payload)
	if err != nil {
		return err
	}

	args := []interface{}{
		e.Key,
		b,
		e.Description,
		e.Importance,
		e.Origin,
		strings.Join(e.Entities, ", "),
		strings.Join(e.OtherReferences, ", "),
		strings.Join(e.Actors, ", "),
		strings.Join(e.Tags, ", "),
	}

	switch e.ID {
	case 0:
		err = tx.Stmt(p.insert).QueryRow(args...).Scan(&e.ID, &e.Created, &e.Updated)
	default:
		args := append(args, e.ID)
		err = tx.Stmt(p.update).QueryRow(args...).Scan(&e.Updated)
	}

	if err != nil {
		return err
	}

	return nil
}

func NewPostgresDataSource(connection string) (*PostgresDataSource, error) {

	p := PostgresDataSource{}

	pg, err := sql.Open("postgres", connection)
	if err != nil {
		return nil, err
	}

	p.pg = pg

	bootstrapDatabase(p.pg)

	s, err := pg.Prepare(insertSQL)
	if err != nil {
		return &p, err
	}
	p.insert = s

	s, err = pg.Prepare(selectByIdSQL)
	if err != nil {
		return &p, err
	}
	p.selectbyid = s

	s, err = pg.Prepare(updateSQL)
	if err != nil {
		return &p, err
	}
	p.update = s

	return &p, nil
}
<commit_msg>moved the queries into a separate file<commit_after>package db

import (
	"database/sql"
	"encoding/json"
	"github.com/StefanKjartansson/eventhub"
	_ "github.com/lib/pq"
	"strings"
)

type PostgresDataSource struct {
	pg         *sql.DB
	insert     *sql.Stmt
	update     *sql.Stmt
	selectbyid *sql.Stmt
}

func scanRow(row *sql.Rows, e *eventhub.Event) error {
	var err error
	var entities StringSlice
	var references StringSlice
	var actors StringSlice
	var tags StringSlice
	temp := []byte{}

	err = row.Scan(
		&e.ID,
		&e.Key,
		&e.Created,
		&e.Updated,
		&temp,
		&e.Description,
		&e.Importance,
		&e.Origin,
		&entities,
		&references,
		&actors,
		&tags)

	if err != nil {
		return err
	}

	var data interface{}
	err = json.Unmarshal(temp, &data)

	if err != nil {
		return err
	}

	e.Payload = data
	e.Entities = entities
	e.OtherReferences = references
	e.Actors = actors
	e.Tags = tags

	return nil

}

func (p *PostgresDataSource) GetById(id int) (*eventhub.Event, error) {

	var e eventhub.Event
	var err error

	err = wrapTransaction(p.pg, func(tx *sql.Tx) error {
		rows, err := tx.Stmt(p.selectbyid).Query(id)
		if err != nil {
			return err
		}
		defer rows.Close()
		if !rows.Next() {
			return sql.ErrNoRows
		}
		return scanRow(rows, &e)
	})

	if err != nil {
		return nil, err
	}
	return &e, nil

}

func (p *PostgresDataSource) Save(e *eventhub.Event) (err error) {

	b, err := json.Marshal(e.Payload)
	if err != nil {
		return err
	}

	args := []interface{}{
		e.Key,
		b,
		e.Description,
		e.Importance,
		e.Origin,
		strings.Join(e.Entities, ", "),
		strings.Join(e.OtherReferences, ", "),
		strings.Join(e.Actors, ", "),
		strings.Join(e.Tags, ", "),
	}

	switch e.ID {
	case 0:
		err = wrapTransaction(p.pg, func(tx *sql.Tx) error {
			return tx.Stmt(p.insert).QueryRow(args...).Scan(&e.ID, &e.Created, &e.Updated)
		})
	default:
		args := append(args, e.ID)
		err = wrapTransaction(p.pg, func(tx *sql.Tx) error {
			return tx.Stmt(p.update).QueryRow(args...).Scan(&e.Updated)
		})
	}

	return err
}

func NewPostgresDataSource(connection string) (*PostgresDataSource, error) {

	p := PostgresDataSource{}

	pg, err := sql.Open("postgres", connection)
	if err != nil {
		return nil, err
	}

	p.pg = pg

	bootstrapDatabase(p.pg)

	s, err := pg.Prepare(insertSQL)
	if err != nil {
		return &p, err
	}
	p.insert = s

	s, err = pg.Prepare(selectByIdSQL)
	if err != nil {
		return &p, err
	}
	p.selectbyid = s

	s, err = pg.Prepare(updateSQL)
	if err != nil {
		return &p, err
	}
	p.update = s

	return &p, nil
}
<|endoftext|>


<commit_before>/*
Copyright The Helm 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 chartutil

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"k8s.io/helm/pkg/proto/hapi/chart"
)

const (
	// ChartfileName is the default Chart file name.
	ChartfileName = "Chart.yaml"
	// ValuesfileName is the default values file name.
	ValuesfileName = "values.yaml"
	// TemplatesDir is the relative directory name for templates.
	TemplatesDir = "templates"
	// ChartsDir is the relative directory name for charts dependencies.
	ChartsDir = "charts"
	// IgnorefileName is the name of the Helm ignore file.
	IgnorefileName = ".helmignore"
	// IngressFileName is the name of the example ingress file.
	IngressFileName = "ingress.yaml"
	// DeploymentName is the name of the example deployment file.
	DeploymentName = "deployment.yaml"
	// ServiceName is the name of the example service file.
	ServiceName = "service.yaml"
	// NotesName is the name of the example NOTES.txt file.
	NotesName = "NOTES.txt"
	// HelpersName is the name of the example helpers file.
	HelpersName = "_helpers.tpl"
	// TemplatesTestsDir is the relative directory name for templates tests.
	TemplatesTestsDir = "templates/tests"
	// TestConnectionName is the name of the example connection test file.
	TestConnectionName = "test-connection.yaml"
)

const defaultValues = `# Default values for %s.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

image:
  repository: nginx
  tag: stable
  pullPolicy: IfNotPresent

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: chart-example.local
      paths: []

  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
  # limits:
  #   cpu: 100m
  #   memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi

nodeSelector: {}

tolerations: []

affinity: {}
`

const defaultIgnore = `# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
`

const defaultIngress = `{{- if .Values.ingress.enabled -}}
{{- $fullName := include "<CHARTNAME>.fullname" . -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: {{ $fullName }}
  labels:
{{ include "<CHARTNAME>.labels" . | indent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
{{- if .Values.ingress.tls }}
  tls:
  {{- range .Values.ingress.tls }}
    - hosts:
      {{- range .hosts }}
        - {{ . | quote }}
      {{- end }}
      secretName: {{ .secretName }}
  {{- end }}
{{- end }}
  rules:
  {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
        {{- range .paths }}
          - path: {{ . }}
            backend:
              serviceName: {{ $fullName }}
              servicePort: http
        {{- end }}
  {{- end }}
{{- end }}
`

const defaultDeployment = `apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "<CHARTNAME>.fullname" . }}
  labels:
{{ include "<CHARTNAME>.labels" . | indent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
      app.kubernetes.io/instance: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
        app.kubernetes.io/instance: {{ .Release.Name }}
    spec:
    {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
    {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
    {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
    {{- end }}
    {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
    {{- end }}
`

const defaultService = `apiVersion: v1
kind: Service
metadata:
  name: {{ include "<CHARTNAME>.fullname" . }}
  labels:
{{ include "<CHARTNAME>.labels" . | indent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
    app.kubernetes.io/instance: {{ .Release.Name }}
`

const defaultNotes = `1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
  {{- range .paths }}
  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
  {{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "<CHARTNAME>.fullname" . }})
  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "<CHARTNAME>.fullname" . }}'
  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "<CHARTNAME>.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "<CHARTNAME>.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl port-forward $POD_NAME 8080:80
{{- end }}
`

const defaultHelpers = `{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "<CHARTNAME>.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "<CHARTNAME>.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "<CHARTNAME>.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Common labels
*/}}
{{- define "<CHARTNAME>.labels" -}}
app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
helm.sh/chart: {{ include "<CHARTNAME>.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion -}}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end -}}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
`

const defaultTestConnection = `apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "<CHARTNAME>.fullname" . }}-test-connection"
  labels:
{{ include "<CHARTNAME>.labels" . | indent 4 }}
  annotations:
    "helm.sh/hook": test-success
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args:  ['{{ include "<CHARTNAME>.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never
`

// CreateFrom creates a new chart, but scaffolds it from the src chart.
func CreateFrom(chartfile *chart.Metadata, dest string, src string) error {
	schart, err := Load(src)
	if err != nil {
		return fmt.Errorf("could not load %s: %s", src, err)
	}

	schart.Metadata = chartfile

	var updatedTemplates []*chart.Template

	for _, template := range schart.Templates {
		newData := Transform(string(template.Data), "<CHARTNAME>", schart.Metadata.Name)
		updatedTemplates = append(updatedTemplates, &chart.Template{Name: template.Name, Data: newData})
	}

	schart.Templates = updatedTemplates
	if schart.Values != nil {
		schart.Values = &chart.Config{Raw: string(Transform(schart.Values.Raw, "<CHARTNAME>", schart.Metadata.Name))}
	}
	return SaveDir(schart, dest)
}

// Create creates a new chart in a directory.
//
// Inside of dir, this will create a directory based on the name of
// chartfile.Name. It will then write the Chart.yaml into this directory and
// create the (empty) appropriate directories.
//
// The returned string will point to the newly created directory. It will be
// an absolute path, even if the provided base directory was relative.
//
// If dir does not exist, this will return an error.
// If Chart.yaml or any directories cannot be created, this will return an
// error. In such a case, this will attempt to clean up by removing the
// new chart directory.
func Create(chartfile *chart.Metadata, dir string) (string, error) {
	path, err := filepath.Abs(dir)
	if err != nil {
		return path, err
	}

	if fi, err := os.Stat(path); err != nil {
		return path, err
	} else if !fi.IsDir() {
		return path, fmt.Errorf("no such directory %s", path)
	}

	n := chartfile.Name
	cdir := filepath.Join(path, n)
	if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() {
		return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir)
	}
	if err := os.MkdirAll(cdir, 0755); err != nil {
		return cdir, err
	}

	cf := filepath.Join(cdir, ChartfileName)
	if _, err := os.Stat(cf); err != nil {
		if err := SaveChartfile(cf, chartfile); err != nil {
			return cdir, err
		}
	}

	for _, d := range []string{TemplatesDir, TemplatesTestsDir, ChartsDir} {
		if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil {
			return cdir, err
		}
	}

	files := []struct {
		path    string
		content []byte
	}{
		{
			// values.yaml
			path:    filepath.Join(cdir, ValuesfileName),
			content: []byte(fmt.Sprintf(defaultValues, chartfile.Name)),
		},
		{
			// .helmignore
			path:    filepath.Join(cdir, IgnorefileName),
			content: []byte(defaultIgnore),
		},
		{
			// ingress.yaml
			path:    filepath.Join(cdir, TemplatesDir, IngressFileName),
			content: Transform(defaultIngress, "<CHARTNAME>", chartfile.Name),
		},
		{
			// deployment.yaml
			path:    filepath.Join(cdir, TemplatesDir, DeploymentName),
			content: Transform(defaultDeployment, "<CHARTNAME>", chartfile.Name),
		},
		{
			// service.yaml
			path:    filepath.Join(cdir, TemplatesDir, ServiceName),
			content: Transform(defaultService, "<CHARTNAME>", chartfile.Name),
		},
		{
			// NOTES.txt
			path:    filepath.Join(cdir, TemplatesDir, NotesName),
			content: Transform(defaultNotes, "<CHARTNAME>", chartfile.Name),
		},
		{
			// _helpers.tpl
			path:    filepath.Join(cdir, TemplatesDir, HelpersName),
			content: Transform(defaultHelpers, "<CHARTNAME>", chartfile.Name),
		},
		{
			// test-connection.yaml
			path:    filepath.Join(cdir, TemplatesTestsDir, TestConnectionName),
			content: Transform(defaultTestConnection, "<CHARTNAME>", chartfile.Name),
		},
	}

	for _, file := range files {
		if _, err := os.Stat(file.path); err == nil {
			// File exists and is okay. Skip it.
			continue
		}
		if err := ioutil.WriteFile(file.path, file.content, 0644); err != nil {
			return cdir, err
		}
	}
	return cdir, nil
}
<commit_msg>Fix scaffold chart label in helper template<commit_after>/*
Copyright The Helm 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 chartutil

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"k8s.io/helm/pkg/proto/hapi/chart"
)

const (
	// ChartfileName is the default Chart file name.
	ChartfileName = "Chart.yaml"
	// ValuesfileName is the default values file name.
	ValuesfileName = "values.yaml"
	// TemplatesDir is the relative directory name for templates.
	TemplatesDir = "templates"
	// ChartsDir is the relative directory name for charts dependencies.
	ChartsDir = "charts"
	// IgnorefileName is the name of the Helm ignore file.
	IgnorefileName = ".helmignore"
	// IngressFileName is the name of the example ingress file.
	IngressFileName = "ingress.yaml"
	// DeploymentName is the name of the example deployment file.
	DeploymentName = "deployment.yaml"
	// ServiceName is the name of the example service file.
	ServiceName = "service.yaml"
	// NotesName is the name of the example NOTES.txt file.
	NotesName = "NOTES.txt"
	// HelpersName is the name of the example helpers file.
	HelpersName = "_helpers.tpl"
	// TemplatesTestsDir is the relative directory name for templates tests.
	TemplatesTestsDir = "templates/tests"
	// TestConnectionName is the name of the example connection test file.
	TestConnectionName = "test-connection.yaml"
)

const defaultValues = `# Default values for %s.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

image:
  repository: nginx
  tag: stable
  pullPolicy: IfNotPresent

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: false
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: chart-example.local
      paths: []

  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
  # limits:
  #   cpu: 100m
  #   memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi

nodeSelector: {}

tolerations: []

affinity: {}
`

const defaultIgnore = `# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
`

const defaultIngress = `{{- if .Values.ingress.enabled -}}
{{- $fullName := include "<CHARTNAME>.fullname" . -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: {{ $fullName }}
  labels:
{{ include "<CHARTNAME>.labels" . | indent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
{{- if .Values.ingress.tls }}
  tls:
  {{- range .Values.ingress.tls }}
    - hosts:
      {{- range .hosts }}
        - {{ . | quote }}
      {{- end }}
      secretName: {{ .secretName }}
  {{- end }}
{{- end }}
  rules:
  {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
        {{- range .paths }}
          - path: {{ . }}
            backend:
              serviceName: {{ $fullName }}
              servicePort: http
        {{- end }}
  {{- end }}
{{- end }}
`

const defaultDeployment = `apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "<CHARTNAME>.fullname" . }}
  labels:
{{ include "<CHARTNAME>.labels" . | indent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
      app.kubernetes.io/instance: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
        app.kubernetes.io/instance: {{ .Release.Name }}
    spec:
    {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
    {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /
              port: http
          readinessProbe:
            httpGet:
              path: /
              port: http
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
    {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
    {{- end }}
    {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
    {{- end }}
`

const defaultService = `apiVersion: v1
kind: Service
metadata:
  name: {{ include "<CHARTNAME>.fullname" . }}
  labels:
{{ include "<CHARTNAME>.labels" . | indent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
    app.kubernetes.io/instance: {{ .Release.Name }}
`

const defaultNotes = `1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
  {{- range .paths }}
  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
  {{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "<CHARTNAME>.fullname" . }})
  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "<CHARTNAME>.fullname" . }}'
  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "<CHARTNAME>.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "<CHARTNAME>.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl port-forward $POD_NAME 8080:80
{{- end }}
`

const defaultHelpers = `{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "<CHARTNAME>.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "<CHARTNAME>.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "<CHARTNAME>.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Common labels
*/}}
{{- define "<CHARTNAME>.labels" -}}
app.kubernetes.io/name: {{ include "<CHARTNAME>.name" . }}
helm.sh/chart: {{ include "<CHARTNAME>.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
`

const defaultTestConnection = `apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "<CHARTNAME>.fullname" . }}-test-connection"
  labels:
{{ include "<CHARTNAME>.labels" . | indent 4 }}
  annotations:
    "helm.sh/hook": test-success
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args:  ['{{ include "<CHARTNAME>.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never
`

// CreateFrom creates a new chart, but scaffolds it from the src chart.
func CreateFrom(chartfile *chart.Metadata, dest string, src string) error {
	schart, err := Load(src)
	if err != nil {
		return fmt.Errorf("could not load %s: %s", src, err)
	}

	schart.Metadata = chartfile

	var updatedTemplates []*chart.Template

	for _, template := range schart.Templates {
		newData := Transform(string(template.Data), "<CHARTNAME>", schart.Metadata.Name)
		updatedTemplates = append(updatedTemplates, &chart.Template{Name: template.Name, Data: newData})
	}

	schart.Templates = updatedTemplates
	if schart.Values != nil {
		schart.Values = &chart.Config{Raw: string(Transform(schart.Values.Raw, "<CHARTNAME>", schart.Metadata.Name))}
	}
	return SaveDir(schart, dest)
}

// Create creates a new chart in a directory.
//
// Inside of dir, this will create a directory based on the name of
// chartfile.Name. It will then write the Chart.yaml into this directory and
// create the (empty) appropriate directories.
//
// The returned string will point to the newly created directory. It will be
// an absolute path, even if the provided base directory was relative.
//
// If dir does not exist, this will return an error.
// If Chart.yaml or any directories cannot be created, this will return an
// error. In such a case, this will attempt to clean up by removing the
// new chart directory.
func Create(chartfile *chart.Metadata, dir string) (string, error) {
	path, err := filepath.Abs(dir)
	if err != nil {
		return path, err
	}

	if fi, err := os.Stat(path); err != nil {
		return path, err
	} else if !fi.IsDir() {
		return path, fmt.Errorf("no such directory %s", path)
	}

	n := chartfile.Name
	cdir := filepath.Join(path, n)
	if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() {
		return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir)
	}
	if err := os.MkdirAll(cdir, 0755); err != nil {
		return cdir, err
	}

	cf := filepath.Join(cdir, ChartfileName)
	if _, err := os.Stat(cf); err != nil {
		if err := SaveChartfile(cf, chartfile); err != nil {
			return cdir, err
		}
	}

	for _, d := range []string{TemplatesDir, TemplatesTestsDir, ChartsDir} {
		if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil {
			return cdir, err
		}
	}

	files := []struct {
		path    string
		content []byte
	}{
		{
			// values.yaml
			path:    filepath.Join(cdir, ValuesfileName),
			content: []byte(fmt.Sprintf(defaultValues, chartfile.Name)),
		},
		{
			// .helmignore
			path:    filepath.Join(cdir, IgnorefileName),
			content: []byte(defaultIgnore),
		},
		{
			// ingress.yaml
			path:    filepath.Join(cdir, TemplatesDir, IngressFileName),
			content: Transform(defaultIngress, "<CHARTNAME>", chartfile.Name),
		},
		{
			// deployment.yaml
			path:    filepath.Join(cdir, TemplatesDir, DeploymentName),
			content: Transform(defaultDeployment, "<CHARTNAME>", chartfile.Name),
		},
		{
			// service.yaml
			path:    filepath.Join(cdir, TemplatesDir, ServiceName),
			content: Transform(defaultService, "<CHARTNAME>", chartfile.Name),
		},
		{
			// NOTES.txt
			path:    filepath.Join(cdir, TemplatesDir, NotesName),
			content: Transform(defaultNotes, "<CHARTNAME>", chartfile.Name),
		},
		{
			// _helpers.tpl
			path:    filepath.Join(cdir, TemplatesDir, HelpersName),
			content: Transform(defaultHelpers, "<CHARTNAME>", chartfile.Name),
		},
		{
			// test-connection.yaml
			path:    filepath.Join(cdir, TemplatesTestsDir, TestConnectionName),
			content: Transform(defaultTestConnection, "<CHARTNAME>", chartfile.Name),
		},
	}

	for _, file := range files {
		if _, err := os.Stat(file.path); err == nil {
			// File exists and is okay. Skip it.
			continue
		}
		if err := ioutil.WriteFile(file.path, file.content, 0644); err != nil {
			return cdir, err
		}
	}
	return cdir, nil
}
<|endoftext|>


<commit_before>package util

import (
	"fmt"
	"reflect"
	"runtime"
	"strings"

	"github.com/nanobox-io/nanobox-server/config"
)

// due to the way this uses the reflect library there are certain assumptions made
// about the interface the function recieves:
// 1. It is of type Struct{}
// 2. Its Kind is one of: Array, Chan, Map, Ptr, or Slice
// 3. The ID (if any) will always be of type String
func UpdateStatus(v interface{}, status string) {

	name := reflect.TypeOf(v).Elem().Name()
	id := reflect.ValueOf(v).Elem().FieldByName("ID").String()

	if id == "" {
		id = "1"
	}

	// allow any messages that were waiting to be sent before me
	runtime.Gosched()
	config.Mist.Publish([]string{"job", strings.ToLower(name)}, fmt.Sprintf(`{"model":"%s", "action":"update", "document":"{\"id\":\"%s\", \"status\":\"%s\"}"}`, name, id, status))
}
<commit_msg>updating model output to be more marshal/unmarshal friendly<commit_after>package util

import (
	"fmt"
	"reflect"
	"runtime"
	"strings"

	"github.com/nanobox-io/nanobox-server/config"
)

// due to the way this uses the reflect library there are certain assumptions made
// about the interface the function recieves:
// 1. It is of type Struct{}
// 2. Its Kind is one of: Array, Chan, Map, Ptr, or Slice
// 3. The ID (if any) will always be of type String
func UpdateStatus(v interface{}, status string) {

	name := reflect.TypeOf(v).Elem().Name()
	id := reflect.ValueOf(v).Elem().FieldByName("ID").String()

	if id == "" {
		id = "1"
	}

	// allow any messages that were waiting to be sent before me
	runtime.Gosched()
	config.Mist.Publish([]string{"job", strings.ToLower(name)}, fmt.Sprintf(`{"model":"%s", "action":"update", "document":{"id":"%s", "status":"%s"}}`, name, id, status))
}
<|endoftext|>


<commit_before>package operator

import (
	"fmt"
	"reflect"

	"github.com/reactiveops/rbac-manager/pkg/apis/rbac-manager/v1beta1"

	"github.com/operator-framework/operator-sdk/pkg/sdk/handler"
	"github.com/operator-framework/operator-sdk/pkg/sdk/types"
	"github.com/sirupsen/logrus"
	rbacv1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"

	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

func NewHandler() handler.Handler {
	// TODO: Finda better place for this
	err := createRbacDefinitionCRD()
	if err != nil {
		logrus.Errorf("Failed to create RbacDefinition CRD: %v", err)
		panic(err.Error())
	}
	return &Handler{}
}

type Handler struct {
	// Fill me
}

func (h *Handler) Handle(ctx types.Context, event types.Event) error {
	switch o := event.Object.(type) {
	case *v1beta1.RbacDefinition:
		err := processRbacDefinition(o)
		if err != nil && !errors.IsAlreadyExists(err) {
			logrus.Errorf("Failed to process RbacDefinition: %v", err)
			return err
		}
	}
	return nil
}

func processRbacDefinition(rbacdef *v1beta1.RbacDefinition) error {
	config, err := rest.InClusterConfig()
	if err != nil {
		panic(err.Error())
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	listOptions := metav1.ListOptions{LabelSelector: "rbac-manager=reactiveops"}
	labels := map[string]string{
		"rbac-manager": "reactiveops",
	}

	existingManagedClusterRoleBindings, err := clientset.RbacV1().ClusterRoleBindings().List(listOptions)
	existingManagedRoleBindings, err := clientset.RbacV1().RoleBindings("").List(listOptions)

	ownerReferences := []metav1.OwnerReference{
		*metav1.NewControllerRef(rbacdef, schema.GroupVersionKind{
			Group:   v1beta1.SchemeGroupVersion.Group,
			Version: v1beta1.SchemeGroupVersion.Version,
			Kind:    "RbacDefinition",
		}),
	}

	logrus.Infof("RbacDefinition %v", rbacdef)

	if rbacdef.Spec.RbacBindings == nil {
		logrus.Warn("No RbacBindings defined")
		return nil
	}

	requestedClusterRoleBindings := []rbacv1.ClusterRoleBinding{}
	requestedRoleBindings := []rbacv1.RoleBinding{}

	for _, rbacBinding := range rbacdef.Spec.RbacBindings {
		if rbacBinding.ClusterRoleBindings != nil {
			for _, requestedCRB := range rbacBinding.ClusterRoleBindings {
				crbName := fmt.Sprintf("%v-%v-%v", rbacdef.Name, rbacBinding.Name, requestedCRB.ClusterRole)

				logrus.Infof("Processing CRB %v")

				requestedClusterRoleBindings = append(requestedClusterRoleBindings, rbacv1.ClusterRoleBinding{
					ObjectMeta: metav1.ObjectMeta{
						Name:            crbName,
						OwnerReferences: ownerReferences,
						Labels:          labels,
					},
					RoleRef: rbacv1.RoleRef{
						Kind: "ClusterRole",
						Name: requestedCRB.ClusterRole,
					},
					Subjects: rbacBinding.Subjects,
				})
			}
		}

		if rbacBinding.RoleBindings != nil {
			for _, requestedRB := range rbacBinding.RoleBindings {
				objectMeta := metav1.ObjectMeta{
					OwnerReferences: ownerReferences,
					Labels:          labels,
				}

				var requestedRoleName string
				var roleRef rbacv1.RoleRef

				if requestedRB.Namespace == "" {
					logrus.Error("Invalid role binding, namespace required")
					break
				}

				objectMeta.Namespace = requestedRB.Namespace

				if requestedRB.ClusterRole != "" {
					logrus.Infof("Processing Requested ClusterRole %v <> %v <> %v", requestedRB.ClusterRole, requestedRB.Namespace, requestedRB)
					requestedRoleName = requestedRB.ClusterRole
					roleRef = rbacv1.RoleRef{
						Kind: "ClusterRole",
						Name: requestedRB.ClusterRole,
					}
				} else if requestedRB.Role != "" {
					logrus.Infof("Processing Requested Role %v <> %v <> %v", requestedRB.Role, requestedRB.Namespace, requestedRB)
					requestedRoleName = fmt.Sprintf("%v-%v", requestedRB.Role, requestedRB.Namespace)
					roleRef = rbacv1.RoleRef{
						Kind: "Role",
						Name: requestedRB.Role,
					}
				} else {
					logrus.Error("Invalid role binding, role or clusterRole required")
					break
				}

				objectMeta.Name = fmt.Sprintf("%v-%v-%v", rbacdef.Name, rbacBinding.Name, requestedRoleName)

				requestedRoleBindings = append(requestedRoleBindings, rbacv1.RoleBinding{
					ObjectMeta: objectMeta,
					RoleRef:    roleRef,
					Subjects:   rbacBinding.Subjects,
				})
			}
		}
	}

	matchingClusterRoleBindings := []rbacv1.ClusterRoleBinding{}

	for _, requestedCRB := range requestedClusterRoleBindings {
		alreadyExists := false
		for _, existingCRB := range existingManagedClusterRoleBindings.Items {
			if crbMatches(&existingCRB, &requestedCRB) {
				alreadyExists = true
				matchingClusterRoleBindings = append(matchingClusterRoleBindings, existingCRB)
				break
			}
		}

		if !alreadyExists {
			logrus.Infof("Attempting to create Cluster Role Binding: %v", requestedCRB)
			crb, err := clientset.RbacV1().ClusterRoleBindings().Create(&requestedCRB)
			if err != nil {
				logrus.Errorf("Error creating Cluster Role Binding: %v", err)
			}
		} else {
			logrus.Infof("Cluster Role Binding already exists %v", requestedCRB)
		}
	}

	for _, existingCRB := range existingManagedClusterRoleBindings.Items {
		if reflect.DeepEqual(existingCRB.OwnerReferences, ownerReferences) {
			matchingRequest := false
			for _, requestedCRB := range matchingClusterRoleBindings {
				if crbMatches(&existingCRB, &requestedCRB) {
					matchingRequest = true
					break
				}
			}

			if !matchingRequest {
				logrus.Infof("Attempting to delete Cluster Role Binding: %v", existingCRB)
				err := clientset.RbacV1().ClusterRoleBindings().Delete(existingCRB.Name, &metav1.DeleteOptions{})
				if err != nil {
					logrus.Errorf("Error deleting Cluster Role Binding: %v", err)
				}
			} else {
				logrus.Infof("Matches requested Cluster Role Binding: %v", err)
			}
		}
	}

	matchingRoleBindings := []rbacv1.RoleBinding{}

	for _, requestedRB := range requestedRoleBindings {
		alreadyExists := false
		for _, existingRB := range existingManagedRoleBindings.Items {
			if rbMatches(&existingRB, &requestedRB) {
				alreadyExists = true
				matchingRoleBindings = append(matchingRoleBindings, existingRB)
				break
			}
		}

		if !alreadyExists {
			logrus.Infof("Attempting to create Role Binding: %v", requestedCRB)
			rb, err := clientset.RbacV1().RoleBindings(requestedRB.ObjectMeta.Namespace).Create(&requestedRB)
			if err != nil {
				logrus.Errorf("Error creating Role Binding: %v", err)
			}
		} else {
			logrus.Infof("Role Binding already exists %v", requestedRB)
		}
	}

	for _, existingRB := range existingManagedRoleBindings.Items {
		if reflect.DeepEqual(existingRB.OwnerReferences, ownerReferences) {
			matchingRequest := false
			for _, requestedRB := range matchingRoleBindings {
				if rbMatches(&existingRB, &requestedRB) {
					matchingRequest = true
					break
				}
			}

			if !matchingRequest {
				logrus.Infof("Attempting to delete Role Binding %v", existingRB)
				err := clientset.RbacV1().ClusterRoleBindings().Delete(existingRB.Name, &metav1.DeleteOptions{})
				if err != nil {
					logrus.Infof("Error deleting Role Binding: %v", err)
				}
			} else {
				logrus.Infof("Matches requested Role Binding %v", existingRb)
			}
		}
	}

	return nil
}
<commit_msg>fixing a couple typos<commit_after>package operator

import (
	"fmt"
	"reflect"

	"github.com/reactiveops/rbac-manager/pkg/apis/rbac-manager/v1beta1"

	"github.com/operator-framework/operator-sdk/pkg/sdk/handler"
	"github.com/operator-framework/operator-sdk/pkg/sdk/types"
	"github.com/sirupsen/logrus"
	rbacv1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"

	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

func NewHandler() handler.Handler {
	// TODO: Finda better place for this
	err := createRbacDefinitionCRD()
	if err != nil {
		logrus.Errorf("Failed to create RbacDefinition CRD: %v", err)
		panic(err.Error())
	}
	return &Handler{}
}

type Handler struct {
	// Fill me
}

func (h *Handler) Handle(ctx types.Context, event types.Event) error {
	switch o := event.Object.(type) {
	case *v1beta1.RbacDefinition:
		err := processRbacDefinition(o)
		if err != nil && !errors.IsAlreadyExists(err) {
			logrus.Errorf("Failed to process RbacDefinition: %v", err)
			return err
		}
	}
	return nil
}

func processRbacDefinition(rbacdef *v1beta1.RbacDefinition) error {
	config, err := rest.InClusterConfig()
	if err != nil {
		panic(err.Error())
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}

	listOptions := metav1.ListOptions{LabelSelector: "rbac-manager=reactiveops"}
	labels := map[string]string{
		"rbac-manager": "reactiveops",
	}

	existingManagedClusterRoleBindings, err := clientset.RbacV1().ClusterRoleBindings().List(listOptions)
	existingManagedRoleBindings, err := clientset.RbacV1().RoleBindings("").List(listOptions)

	ownerReferences := []metav1.OwnerReference{
		*metav1.NewControllerRef(rbacdef, schema.GroupVersionKind{
			Group:   v1beta1.SchemeGroupVersion.Group,
			Version: v1beta1.SchemeGroupVersion.Version,
			Kind:    "RbacDefinition",
		}),
	}

	logrus.Infof("RbacDefinition %v", rbacdef)

	if rbacdef.Spec.RbacBindings == nil {
		logrus.Warn("No RbacBindings defined")
		return nil
	}

	requestedClusterRoleBindings := []rbacv1.ClusterRoleBinding{}
	requestedRoleBindings := []rbacv1.RoleBinding{}

	for _, rbacBinding := range rbacdef.Spec.RbacBindings {
		if rbacBinding.ClusterRoleBindings != nil {
			for _, requestedCRB := range rbacBinding.ClusterRoleBindings {
				crbName := fmt.Sprintf("%v-%v-%v", rbacdef.Name, rbacBinding.Name, requestedCRB.ClusterRole)

				logrus.Infof("Processing CRB %v")

				requestedClusterRoleBindings = append(requestedClusterRoleBindings, rbacv1.ClusterRoleBinding{
					ObjectMeta: metav1.ObjectMeta{
						Name:            crbName,
						OwnerReferences: ownerReferences,
						Labels:          labels,
					},
					RoleRef: rbacv1.RoleRef{
						Kind: "ClusterRole",
						Name: requestedCRB.ClusterRole,
					},
					Subjects: rbacBinding.Subjects,
				})
			}
		}

		if rbacBinding.RoleBindings != nil {
			for _, requestedRB := range rbacBinding.RoleBindings {
				objectMeta := metav1.ObjectMeta{
					OwnerReferences: ownerReferences,
					Labels:          labels,
				}

				var requestedRoleName string
				var roleRef rbacv1.RoleRef

				if requestedRB.Namespace == "" {
					logrus.Error("Invalid role binding, namespace required")
					break
				}

				objectMeta.Namespace = requestedRB.Namespace

				if requestedRB.ClusterRole != "" {
					logrus.Infof("Processing Requested ClusterRole %v <> %v <> %v", requestedRB.ClusterRole, requestedRB.Namespace, requestedRB)
					requestedRoleName = requestedRB.ClusterRole
					roleRef = rbacv1.RoleRef{
						Kind: "ClusterRole",
						Name: requestedRB.ClusterRole,
					}
				} else if requestedRB.Role != "" {
					logrus.Infof("Processing Requested Role %v <> %v <> %v", requestedRB.Role, requestedRB.Namespace, requestedRB)
					requestedRoleName = fmt.Sprintf("%v-%v", requestedRB.Role, requestedRB.Namespace)
					roleRef = rbacv1.RoleRef{
						Kind: "Role",
						Name: requestedRB.Role,
					}
				} else {
					logrus.Error("Invalid role binding, role or clusterRole required")
					break
				}

				objectMeta.Name = fmt.Sprintf("%v-%v-%v", rbacdef.Name, rbacBinding.Name, requestedRoleName)

				requestedRoleBindings = append(requestedRoleBindings, rbacv1.RoleBinding{
					ObjectMeta: objectMeta,
					RoleRef:    roleRef,
					Subjects:   rbacBinding.Subjects,
				})
			}
		}
	}

	matchingClusterRoleBindings := []rbacv1.ClusterRoleBinding{}

	for _, requestedCRB := range requestedClusterRoleBindings {
		alreadyExists := false
		for _, existingCRB := range existingManagedClusterRoleBindings.Items {
			if crbMatches(&existingCRB, &requestedCRB) {
				alreadyExists = true
				matchingClusterRoleBindings = append(matchingClusterRoleBindings, existingCRB)
				break
			}
		}

		if !alreadyExists {
			logrus.Infof("Attempting to create Cluster Role Binding: %v", requestedCRB)
			crb, err := clientset.RbacV1().ClusterRoleBindings().Create(&requestedCRB)
			if err != nil {
				logrus.Errorf("Error creating Cluster Role Binding: %v", err)
			}
		} else {
			logrus.Infof("Cluster Role Binding already exists %v", requestedCRB)
		}
	}

	for _, existingCRB := range existingManagedClusterRoleBindings.Items {
		if reflect.DeepEqual(existingCRB.OwnerReferences, ownerReferences) {
			matchingRequest := false
			for _, requestedCRB := range matchingClusterRoleBindings {
				if crbMatches(&existingCRB, &requestedCRB) {
					matchingRequest = true
					break
				}
			}

			if !matchingRequest {
				logrus.Infof("Attempting to delete Cluster Role Binding: %v", existingCRB)
				err := clientset.RbacV1().ClusterRoleBindings().Delete(existingCRB.Name, &metav1.DeleteOptions{})
				if err != nil {
					logrus.Errorf("Error deleting Cluster Role Binding: %v", err)
				}
			} else {
				logrus.Infof("Matches requested Cluster Role Binding: %v", err)
			}
		}
	}

	matchingRoleBindings := []rbacv1.RoleBinding{}

	for _, requestedRB := range requestedRoleBindings {
		alreadyExists := false
		for _, existingRB := range existingManagedRoleBindings.Items {
			if rbMatches(&existingRB, &requestedRB) {
				alreadyExists = true
				matchingRoleBindings = append(matchingRoleBindings, existingRB)
				break
			}
		}

		if !alreadyExists {
			logrus.Infof("Attempting to create Role Binding: %v", requestedRB)
			rb, err := clientset.RbacV1().RoleBindings(requestedRB.ObjectMeta.Namespace).Create(&requestedRB)
			if err != nil {
				logrus.Errorf("Error creating Role Binding: %v", err)
			}
		} else {
			logrus.Infof("Role Binding already exists %v", requestedRB)
		}
	}

	for _, existingRB := range existingManagedRoleBindings.Items {
		if reflect.DeepEqual(existingRB.OwnerReferences, ownerReferences) {
			matchingRequest := false
			for _, requestedRB := range matchingRoleBindings {
				if rbMatches(&existingRB, &requestedRB) {
					matchingRequest = true
					break
				}
			}

			if !matchingRequest {
				logrus.Infof("Attempting to delete Role Binding %v", existingRB)
				err := clientset.RbacV1().ClusterRoleBindings().Delete(existingRB.Name, &metav1.DeleteOptions{})
				if err != nil {
					logrus.Infof("Error deleting Role Binding: %v", err)
				}
			} else {
				logrus.Infof("Matches requested Role Binding %v", existingRB)
			}
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package rbac

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/rancher/norman/types"
	v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3"
	rbacv1 "k8s.io/api/rbac/v1"
)

func Test_BuildSubjectFromRTB(t *testing.T) {
	type testCase struct {
		from  interface{}
		to    rbacv1.Subject
		iserr bool
	}

	userSubject := rbacv1.Subject{
		Kind: "User",
		Name: "tmp-user",
	}

	groupSubject := rbacv1.Subject{
		Kind: "Group",
		Name: "tmp-group",
	}

	saSubject := rbacv1.Subject{
		Kind:      "ServiceAccount",
		Name:      "tmp-sa",
		Namespace: "tmp-namespace",
	}

	testCases := []testCase{
		testCase{
			from:  nil,
			iserr: true,
		},
		testCase{
			from: &v3.ProjectRoleTemplateBinding{
				UserName: userSubject.Name,
			},
			to: userSubject,
		},
		testCase{
			from: &v3.ProjectRoleTemplateBinding{
				GroupName: groupSubject.Name,
			},
			to: groupSubject,
		},
		testCase{
			from: &v3.ProjectRoleTemplateBinding{
				ServiceAccount: fmt.Sprintf("%s:%s", saSubject.Namespace, saSubject.Name),
			},
			to: saSubject,
		},
		testCase{
			from: &v3.ClusterRoleTemplateBinding{
				UserName: userSubject.Name,
			},
			to: userSubject,
		},
		testCase{
			from: &v3.ClusterRoleTemplateBinding{
				GroupName: groupSubject.Name,
			},
			to: groupSubject,
		},
		testCase{
			from: &v3.ProjectRoleTemplateBinding{
				ServiceAccount: "wrong-format",
			},
			iserr: true,
		},
	}

	for _, tcase := range testCases {
		output, err := BuildSubjectFromRTB(tcase.from)
		if tcase.iserr && err == nil {
			t.Errorf("roletemplatebinding %v should return error", tcase.from)
		} else if !tcase.iserr && !reflect.DeepEqual(tcase.to, output) {
			t.Errorf("the subject %v from roletemplatebinding %v is mismatched, expect %v", output, tcase.from, tcase.to)
		}
	}
}

func Test_TypeFromContext(t *testing.T) {
	type testCase struct {
		apiContext   *types.APIContext
		resource     *types.RawResource
		expectedType string
	}

	testCases := []testCase{
		{
			apiContext: &types.APIContext{
				Type: "catalog",
			},
			resource:     nil,
			expectedType: "catalog",
		},
		{
			apiContext: &types.APIContext{
				Type: "subscribe",
			},
			resource: &types.RawResource{
				Type: "catalog",
			},
			expectedType: "catalog",
		},
	}

	for _, tcase := range testCases {
		outputType := TypeFromContext(tcase.apiContext, tcase.resource)
		if tcase.expectedType != outputType {
			t.Errorf("resource type %s is mismatched, expect %s", outputType, tcase.expectedType)
		}
	}
}
<commit_msg>Update test for role change<commit_after>package rbac

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/rancher/norman/types"
	v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3"
	rbacv1 "k8s.io/api/rbac/v1"
)

func Test_BuildSubjectFromRTB(t *testing.T) {
	type testCase struct {
		from  interface{}
		to    rbacv1.Subject
		iserr bool
	}

	userSubject := rbacv1.Subject{
		Kind:     "User",
		Name:     "tmp-user",
		APIGroup: "rbac.authorization.k8s.io",
	}

	groupSubject := rbacv1.Subject{
		Kind:     "Group",
		Name:     "tmp-group",
		APIGroup: "rbac.authorization.k8s.io",
	}

	saSubject := rbacv1.Subject{
		Kind:      "ServiceAccount",
		Name:      "tmp-sa",
		Namespace: "tmp-namespace",
		APIGroup:  "",
	}

	testCases := []testCase{
		testCase{
			from:  nil,
			iserr: true,
		},
		testCase{
			from: &v3.ProjectRoleTemplateBinding{
				UserName: userSubject.Name,
			},
			to: userSubject,
		},
		testCase{
			from: &v3.ProjectRoleTemplateBinding{
				GroupName: groupSubject.Name,
			},
			to: groupSubject,
		},
		testCase{
			from: &v3.ProjectRoleTemplateBinding{
				ServiceAccount: fmt.Sprintf("%s:%s", saSubject.Namespace, saSubject.Name),
			},
			to: saSubject,
		},
		testCase{
			from: &v3.ClusterRoleTemplateBinding{
				UserName: userSubject.Name,
			},
			to: userSubject,
		},
		testCase{
			from: &v3.ClusterRoleTemplateBinding{
				GroupName: groupSubject.Name,
			},
			to: groupSubject,
		},
		testCase{
			from: &v3.ProjectRoleTemplateBinding{
				ServiceAccount: "wrong-format",
			},
			iserr: true,
		},
	}

	for _, tcase := range testCases {
		output, err := BuildSubjectFromRTB(tcase.from)
		if tcase.iserr && err == nil {
			t.Errorf("roletemplatebinding %v should return error", tcase.from)
		} else if !tcase.iserr && !reflect.DeepEqual(tcase.to, output) {
			t.Errorf("the subject %v from roletemplatebinding %v is mismatched, expect %v", output, tcase.from, tcase.to)
		}
	}
}

func Test_TypeFromContext(t *testing.T) {
	type testCase struct {
		apiContext   *types.APIContext
		resource     *types.RawResource
		expectedType string
	}

	testCases := []testCase{
		{
			apiContext: &types.APIContext{
				Type: "catalog",
			},
			resource:     nil,
			expectedType: "catalog",
		},
		{
			apiContext: &types.APIContext{
				Type: "subscribe",
			},
			resource: &types.RawResource{
				Type: "catalog",
			},
			expectedType: "catalog",
		},
	}

	for _, tcase := range testCases {
		outputType := TypeFromContext(tcase.apiContext, tcase.resource)
		if tcase.expectedType != outputType {
			t.Errorf("resource type %s is mismatched, expect %s", outputType, tcase.expectedType)
		}
	}
}
<|endoftext|>


<commit_before>package restapi_test

import (
	"context"
	"encoding/json"
	"net"
	"net/http"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/datawire/dlib/dlog"
	"github.com/telepresenceio/telepresence/v2/pkg/restapi"
)

type yesNoClient bool
type yesNoCluster bool

func (yn yesNoClient) InterceptInfo(_ context.Context, _ string, _ http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: bool(yn), ClientSide: true}, nil
}

func (yn yesNoCluster) InterceptInfo(_ context.Context, _ string, _ http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: bool(yn), ClientSide: false}, nil
}

type textMatcher map[string]string
type textMatcherClient textMatcher
type textMatcherCluster textMatcher

func (t textMatcher) intercepted(header http.Header) bool {
	for k, v := range t {
		if header.Get(k) != v {
			return false
		}
	}
	return true
}

func (t textMatcherClient) InterceptInfo(_ context.Context, _ string, header http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: textMatcher(t).intercepted(header), ClientSide: true}, nil
}

func (t textMatcherCluster) InterceptInfo(_ context.Context, _ string, header http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: textMatcher(t).intercepted(header), ClientSide: false}, nil
}

type matcherWithMetadata struct {
	textMatcherCluster
	meta map[string]string
}

func (t *matcherWithMetadata) InterceptInfo(c context.Context, p string, header http.Header) (*restapi.InterceptInfo, error) {
	ret, _ := t.textMatcherCluster.InterceptInfo(c, p, header)
	ret.Metadata = t.meta
	return ret, nil
}

type callerIdMatcher string

func (c callerIdMatcher) InterceptInfo(_ context.Context, callerId string, _ http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: callerId == string(c), ClientSide: true}, nil
}

type callerIdMatcherCluster string

func (c callerIdMatcherCluster) InterceptInfo(_ context.Context, callerId string, _ http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: callerId == string(c), ClientSide: false}, nil
}

func Test_server_intercepts(t *testing.T) {
	tests := []struct {
		name     string
		agent    restapi.AgentState
		headers  map[string]string
		endpoint string
		want     interface{}
	}{
		{
			"client true",
			yesNoClient(true),
			nil,
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client false",
			yesNoClient(false),
			nil,
			restapi.EndPointConsumeHere,
			false,
		},
		{
			"cluster true",
			yesNoCluster(true),
			nil,
			restapi.EndPointConsumeHere,
			false,
		},
		{
			"cluster false",
			yesNoCluster(false),
			nil,
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client header - match",
			textMatcherClient{
				restapi.HeaderInterceptID: "abc:123",
			},
			map[string]string{
				restapi.HeaderInterceptID: "abc:123",
			},
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client header - no match",
			textMatcherClient{
				restapi.HeaderInterceptID: "abc:123",
			},
			map[string]string{
				restapi.HeaderInterceptID: "xyz:123",
			},
			restapi.EndPointConsumeHere,
			false,
		},
		{
			"cluster header - match",
			textMatcherCluster{
				restapi.HeaderInterceptID: "abc:123",
			},
			map[string]string{
				restapi.HeaderInterceptID: "abc:123",
			},
			restapi.EndPointConsumeHere,
			false,
		},
		{
			"cluster header - match",
			&matcherWithMetadata{
				textMatcherCluster: textMatcherCluster{
					restapi.HeaderInterceptID: "abc:123",
				},
				meta: map[string]string{
					"a": "A",
				},
			},
			map[string]string{
				restapi.HeaderInterceptID: "abc:123",
			},
			restapi.EndPointInterceptInfo,
			&restapi.InterceptInfo{
				Intercepted: true,
				ClientSide:  false,
				Metadata: map[string]string{
					"a": "A",
				},
			},
		},
		{
			"cluster header - no match",
			textMatcherCluster{
				restapi.HeaderInterceptID: "abc:123",
			},
			map[string]string{
				restapi.HeaderInterceptID: "xyz:123",
			},
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client multi header - all matched",
			textMatcherClient{
				"header-a": "value-a",
				"header-b": "value-b",
			},
			map[string]string{
				"header-a": "value-a",
				"header-b": "value-b",
				"header-c": "value-c",
			},
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client multi header - not all matched",
			textMatcherClient{
				"header-a": "value-a",
				"header-b": "value-b",
				"header-c": "value-c",
			},
			map[string]string{
				"header-a": "value-a",
				"header-b": "value-b",
			},
			restapi.EndPointConsumeHere,
			false,
		},
		{
			"client caller intercept id - match",
			callerIdMatcher("abc:123"),
			map[string]string{
				restapi.HeaderCallerInterceptID: "abc:123",
			},
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client caller intercept id - match",
			callerIdMatcher("abc:123"),
			map[string]string{
				restapi.HeaderCallerInterceptID: "abc:123",
			},
			restapi.EndPointInterceptInfo,
			&restapi.InterceptInfo{
				Intercepted: true,
				ClientSide:  true,
				Metadata:    nil,
			},
		},
		{
			"cluster caller intercept id - match",
			callerIdMatcherCluster("abc:123"),
			map[string]string{
				restapi.HeaderCallerInterceptID: "abc:123",
			},
			restapi.EndPointInterceptInfo,
			&restapi.InterceptInfo{
				Intercepted: true,
				ClientSide:  false,
				Metadata:    nil,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c, cancel := context.WithCancel(dlog.NewTestContext(t, false))
			ln, err := net.Listen("tcp", ":0")
			require.NoError(t, err)
			wg := sync.WaitGroup{}
			wg.Add(1)
			go func() {
				defer wg.Done()
				assert.NoError(t, restapi.NewServer(tt.agent).Serve(c, ln))
			}()
			rq, err := http.NewRequest(http.MethodGet, "http://"+ln.Addr().String()+tt.endpoint, nil)
			for k, v := range tt.headers {
				rq.Header.Set(k, v)
			}
			require.NoError(t, err)
			r, err := http.DefaultClient.Do(rq)
			require.NoError(t, err)
			defer r.Body.Close()
			assert.Equal(t, r.StatusCode, http.StatusOK)
			if _, ok := tt.want.(bool); ok {
				var rpl bool
				require.NoError(t, json.NewDecoder(r.Body).Decode(&rpl))
				assert.Equal(t, tt.want, rpl)
			} else {
				var rpl restapi.InterceptInfo
				require.NoError(t, json.NewDecoder(r.Body).Decode(&rpl))
				assert.Equal(t, tt.want, &rpl)
			}
			cancel()
			wg.Wait()
		})
	}
}
<commit_msg>Rename callerIdMatcher to callerIdMatcherClient in rest API tests<commit_after>package restapi_test

import (
	"context"
	"encoding/json"
	"net"
	"net/http"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/datawire/dlib/dlog"
	"github.com/telepresenceio/telepresence/v2/pkg/restapi"
)

type yesNoClient bool
type yesNoCluster bool

func (yn yesNoClient) InterceptInfo(_ context.Context, _ string, _ http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: bool(yn), ClientSide: true}, nil
}

func (yn yesNoCluster) InterceptInfo(_ context.Context, _ string, _ http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: bool(yn), ClientSide: false}, nil
}

type textMatcher map[string]string
type textMatcherClient textMatcher
type textMatcherCluster textMatcher

func (t textMatcher) intercepted(header http.Header) bool {
	for k, v := range t {
		if header.Get(k) != v {
			return false
		}
	}
	return true
}

func (t textMatcherClient) InterceptInfo(_ context.Context, _ string, header http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: textMatcher(t).intercepted(header), ClientSide: true}, nil
}

func (t textMatcherCluster) InterceptInfo(_ context.Context, _ string, header http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: textMatcher(t).intercepted(header), ClientSide: false}, nil
}

type matcherWithMetadata struct {
	textMatcherCluster
	meta map[string]string
}

func (t *matcherWithMetadata) InterceptInfo(c context.Context, p string, header http.Header) (*restapi.InterceptInfo, error) {
	ret, _ := t.textMatcherCluster.InterceptInfo(c, p, header)
	ret.Metadata = t.meta
	return ret, nil
}

type callerIdMatcherClient string

func (c callerIdMatcherClient) InterceptInfo(_ context.Context, callerId string, _ http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: callerId == string(c), ClientSide: true}, nil
}

type callerIdMatcherCluster string

func (c callerIdMatcherCluster) InterceptInfo(_ context.Context, callerId string, _ http.Header) (*restapi.InterceptInfo, error) {
	return &restapi.InterceptInfo{Intercepted: callerId == string(c), ClientSide: false}, nil
}

func Test_server_intercepts(t *testing.T) {
	tests := []struct {
		name     string
		agent    restapi.AgentState
		headers  map[string]string
		endpoint string
		want     interface{}
	}{
		{
			"client true",
			yesNoClient(true),
			nil,
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client false",
			yesNoClient(false),
			nil,
			restapi.EndPointConsumeHere,
			false,
		},
		{
			"cluster true",
			yesNoCluster(true),
			nil,
			restapi.EndPointConsumeHere,
			false,
		},
		{
			"cluster false",
			yesNoCluster(false),
			nil,
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client header - match",
			textMatcherClient{
				restapi.HeaderInterceptID: "abc:123",
			},
			map[string]string{
				restapi.HeaderInterceptID: "abc:123",
			},
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client header - no match",
			textMatcherClient{
				restapi.HeaderInterceptID: "abc:123",
			},
			map[string]string{
				restapi.HeaderInterceptID: "xyz:123",
			},
			restapi.EndPointConsumeHere,
			false,
		},
		{
			"cluster header - match",
			textMatcherCluster{
				restapi.HeaderInterceptID: "abc:123",
			},
			map[string]string{
				restapi.HeaderInterceptID: "abc:123",
			},
			restapi.EndPointConsumeHere,
			false,
		},
		{
			"cluster header - match",
			&matcherWithMetadata{
				textMatcherCluster: textMatcherCluster{
					restapi.HeaderInterceptID: "abc:123",
				},
				meta: map[string]string{
					"a": "A",
				},
			},
			map[string]string{
				restapi.HeaderInterceptID: "abc:123",
			},
			restapi.EndPointInterceptInfo,
			&restapi.InterceptInfo{
				Intercepted: true,
				ClientSide:  false,
				Metadata: map[string]string{
					"a": "A",
				},
			},
		},
		{
			"cluster header - no match",
			textMatcherCluster{
				restapi.HeaderInterceptID: "abc:123",
			},
			map[string]string{
				restapi.HeaderInterceptID: "xyz:123",
			},
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client multi header - all matched",
			textMatcherClient{
				"header-a": "value-a",
				"header-b": "value-b",
			},
			map[string]string{
				"header-a": "value-a",
				"header-b": "value-b",
				"header-c": "value-c",
			},
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client multi header - not all matched",
			textMatcherClient{
				"header-a": "value-a",
				"header-b": "value-b",
				"header-c": "value-c",
			},
			map[string]string{
				"header-a": "value-a",
				"header-b": "value-b",
			},
			restapi.EndPointConsumeHere,
			false,
		},
		{
			"client caller intercept id - match",
			callerIdMatcherClient("abc:123"),
			map[string]string{
				restapi.HeaderCallerInterceptID: "abc:123",
			},
			restapi.EndPointConsumeHere,
			true,
		},
		{
			"client caller intercept id - match",
			callerIdMatcherClient("abc:123"),
			map[string]string{
				restapi.HeaderCallerInterceptID: "abc:123",
			},
			restapi.EndPointInterceptInfo,
			&restapi.InterceptInfo{
				Intercepted: true,
				ClientSide:  true,
				Metadata:    nil,
			},
		},
		{
			"cluster caller intercept id - match",
			callerIdMatcherCluster("abc:123"),
			map[string]string{
				restapi.HeaderCallerInterceptID: "abc:123",
			},
			restapi.EndPointInterceptInfo,
			&restapi.InterceptInfo{
				Intercepted: true,
				ClientSide:  false,
				Metadata:    nil,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			c, cancel := context.WithCancel(dlog.NewTestContext(t, false))
			ln, err := net.Listen("tcp", ":0")
			require.NoError(t, err)
			wg := sync.WaitGroup{}
			wg.Add(1)
			go func() {
				defer wg.Done()
				assert.NoError(t, restapi.NewServer(tt.agent).Serve(c, ln))
			}()
			rq, err := http.NewRequest(http.MethodGet, "http://"+ln.Addr().String()+tt.endpoint, nil)
			for k, v := range tt.headers {
				rq.Header.Set(k, v)
			}
			require.NoError(t, err)
			r, err := http.DefaultClient.Do(rq)
			require.NoError(t, err)
			defer r.Body.Close()
			assert.Equal(t, r.StatusCode, http.StatusOK)
			if _, ok := tt.want.(bool); ok {
				var rpl bool
				require.NoError(t, json.NewDecoder(r.Body).Decode(&rpl))
				assert.Equal(t, tt.want, rpl)
			} else {
				var rpl restapi.InterceptInfo
				require.NoError(t, json.NewDecoder(r.Body).Decode(&rpl))
				assert.Equal(t, tt.want, &rpl)
			}
			cancel()
			wg.Wait()
		})
	}
}
<|endoftext|>


<commit_before>package rkeworker

import (
	"context"
	"io"
	"reflect"
	"strings"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
	"github.com/rancher/rke/services"
	"github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/sirupsen/logrus"
)

const (
	RKEContainerNameLabel  = "io.rancher.rke.container.name"
	CattleProcessNameLabel = "io.cattle.process.name"
)

type NodeConfig struct {
	ClusterName string                `json:"clusterName"`
	Certs       string                `json:"certs"`
	Processes   map[string]v3.Process `json:"processes"`
	Files       []v3.File             `json:"files"`
}

func runProcess(ctx context.Context, name string, p v3.Process, start bool) error {
	c, err := client.NewEnvClient()
	if err != nil {
		return err
	}
	defer c.Close()

	args := filters.NewArgs()
	args.Add("label", RKEContainerNameLabel+"="+name)
	// to handle upgrades of old container
	oldArgs := filters.NewArgs()
	oldArgs.Add("label", CattleProcessNameLabel+"="+name)

	containers, err := c.ContainerList(ctx, types.ContainerListOptions{
		All:     true,
		Filters: args,
	})
	if err != nil {
		return err
	}
	oldContainers, err := c.ContainerList(ctx, types.ContainerListOptions{
		All:     true,
		Filters: oldArgs,
	})
	if err != nil {
		return err
	}
	containers = append(containers, oldContainers...)
	var matchedContainers []types.Container
	for _, container := range containers {
		changed, err := changed(ctx, c, p, container)
		if err != nil {
			return err
		}

		if changed {
			err := remove(ctx, c, container.ID)
			if err != nil {
				return err
			}
		} else {
			matchedContainers = append(matchedContainers, container)
		}
	}

	for i := 1; i < len(matchedContainers); i++ {
		if err := remove(ctx, c, matchedContainers[i].ID); err != nil {
			return err
		}
	}

	if len(matchedContainers) > 0 {
		if strings.Contains(name, "share-mnt") {
			inspect, err := c.ContainerInspect(ctx, matchedContainers[0].ID)
			if err != nil {
				return err
			}
			if inspect.State != nil && inspect.State.Status == "exited" && inspect.State.ExitCode == 0 {
				return nil
			}
		}
		c.ContainerStart(ctx, matchedContainers[0].ID, types.ContainerStartOptions{})
		return nil
	}

	config, hostConfig, _ := services.GetProcessConfig(p)
	if config.Labels == nil {
		config.Labels = map[string]string{}
	}
	config.Labels[RKEContainerNameLabel] = name

	newContainer, err := c.ContainerCreate(ctx, config, hostConfig, nil, name)
	if client.IsErrImageNotFound(err) {
		var output io.ReadCloser
		imagePullOptions := types.ImagePullOptions{}
		if p.ImageRegistryAuthConfig != "" {
			imagePullOptions.RegistryAuth = p.ImageRegistryAuthConfig
			imagePullOptions.PrivilegeFunc = func() (string, error) { return p.ImageRegistryAuthConfig, nil }
		}
		output, err = c.ImagePull(ctx, config.Image, imagePullOptions)
		if err != nil {
			return err
		}
		defer output.Close()
		io.Copy(logrus.StandardLogger().Writer(), output)
		newContainer, err = c.ContainerCreate(ctx, config, hostConfig, nil, name)
	}
	if err == nil && start {
		return c.ContainerStart(ctx, newContainer.ID, types.ContainerStartOptions{})
	}
	return err
}

func remove(ctx context.Context, c *client.Client, id string) error {
	return c.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
		Force:         true,
		RemoveVolumes: true,
	})
}

func changed(ctx context.Context, c *client.Client, expectedProcess v3.Process, container types.Container) (bool, error) {
	actualDockerInspect, err := c.ContainerInspect(ctx, container.ID)
	if err != nil {
		return false, err
	}
	defaultDockerInspect, _, err := c.ImageInspectWithRaw(ctx, actualDockerInspect.Image)
	if err != nil {
		return false, err
	}
	actualProcess := v3.Process{
		Command:     actualDockerInspect.Config.Entrypoint,
		Args:        actualDockerInspect.Config.Cmd,
		Env:         actualDockerInspect.Config.Env,
		Image:       actualDockerInspect.Config.Image,
		Binds:       actualDockerInspect.HostConfig.Binds,
		NetworkMode: string(actualDockerInspect.HostConfig.NetworkMode),
		PidMode:     string(actualDockerInspect.HostConfig.PidMode),
		Privileged:  actualDockerInspect.HostConfig.Privileged,
		VolumesFrom: actualDockerInspect.HostConfig.VolumesFrom,
		Labels:      actualDockerInspect.Config.Labels,
	}

	if len(expectedProcess.Command) == 0 {
		expectedProcess.Command = actualProcess.Command
	}
	if len(expectedProcess.Args) == 0 {
		expectedProcess.Args = actualProcess.Args
	}
	if len(expectedProcess.Env) == 0 {
		expectedProcess.Env = actualProcess.Env
	}
	if expectedProcess.NetworkMode == "" {
		expectedProcess.NetworkMode = actualProcess.NetworkMode
	}
	if expectedProcess.PidMode == "" {
		expectedProcess.PidMode = actualProcess.PidMode
	}
	if len(expectedProcess.Labels) == 0 {
		expectedProcess.Labels = actualProcess.Labels
	}

	// Don't detect changes on these fields
	actualProcess.Name = expectedProcess.Name
	actualProcess.HealthCheck.URL = expectedProcess.HealthCheck.URL
	actualProcess.RestartPolicy = expectedProcess.RestartPolicy

	changed := false
	t := reflect.TypeOf(actualProcess)
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		if f.Name == "Command" {
			leftMap := sliceToMap(expectedProcess.Command)
			rightMap := sliceToMap(actualProcess.Command)

			if reflect.DeepEqual(leftMap, rightMap) {
				continue
			}
		} else if f.Name == "Env" {
			expectedEnvs := make(map[string]string, 8)
			for _, env := range defaultDockerInspect.Config.Env {
				es := strings.SplitN(env, "=", 2)
				expectedEnvs[es[0]] = es[1]
			}
			for _, env := range expectedProcess.Env {
				es := strings.SplitN(env, "=", 2)
				expectedEnvs[es[0]] = es[1]
			}

			actualEnvs := make(map[string]string, 8)
			for _, env := range actualProcess.Env {
				es := strings.SplitN(env, "=", 2)
				actualEnvs[es[0]] = es[1]
			}

			if reflect.DeepEqual(expectedEnvs, actualEnvs) {
				continue
			}
		} else if f.Name == "Labels" {
			processLabels := make(map[string]string)
			for k, v := range defaultDockerInspect.Config.Labels {
				processLabels[k] = v
			}
			for k, v := range expectedProcess.Labels {
				processLabels[k] = v
			}

			if reflect.DeepEqual(processLabels, actualProcess.Labels) {
				continue
			}
		} else if f.Name == "Publish" {
			expectedExposedPortSet, expectedBindings, err := nat.ParsePortSpecs(reflect.ValueOf(expectedProcess).Field(i).Interface().([]string))
			if err != nil {
				return false, err
			}
			expectedExposedPorts := natPortSetToSlice(expectedExposedPortSet)
			nat.SortPortMap(expectedExposedPorts, expectedBindings)

			actualExposedPortSet := make(map[nat.Port]struct{}, 0)
			actualBindings := actualDockerInspect.HostConfig.PortBindings
			for port := range actualBindings {
				if _, exists := actualExposedPortSet[port]; !exists {
					actualExposedPortSet[port] = struct{}{}
				}
			}
			actualExposedPorts := natPortSetToSlice(actualExposedPortSet)
			nat.SortPortMap(actualExposedPorts, actualBindings)

			if reflect.DeepEqual(actualBindings, nat.PortMap(expectedBindings)) {
				continue
			}
		}

		left := reflect.ValueOf(actualProcess).Field(i).Interface()
		right := reflect.ValueOf(expectedProcess).Field(i).Interface()
		if !reflect.DeepEqual(left, right) {
			logrus.Infof("For process %s, %s has changed from %v to %v", expectedProcess.Name, f.Name, right, left)
			changed = true
		}
	}

	return changed, nil
}

func sliceToMap(args []string) map[string]bool {
	result := map[string]bool{}
	for _, arg := range args {
		result[arg] = true
	}
	return result
}

func natPortSetToSlice(args map[nat.Port]struct{}) []nat.Port {
	result := make([]nat.Port, 0, len(args))
	for arg := range args {
		result = append(result, arg)
	}
	return result
}
<commit_msg>Run log linker with worker process<commit_after>package rkeworker

import (
	"context"
	"fmt"
	"io"
	"reflect"
	"strings"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/client"

	"github.com/docker/go-connections/nat"
	"github.com/rancher/rke/hosts"
	"github.com/rancher/rke/services"
	"github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/sirupsen/logrus"
)

const (
	RKEContainerNameLabel  = "io.rancher.rke.container.name"
	CattleProcessNameLabel = "io.cattle.process.name"
)

type NodeConfig struct {
	ClusterName string                `json:"clusterName"`
	Certs       string                `json:"certs"`
	Processes   map[string]v3.Process `json:"processes"`
	Files       []v3.File             `json:"files"`
}

func runProcess(ctx context.Context, name string, p v3.Process, start bool) error {
	c, err := client.NewEnvClient()
	if err != nil {
		return err
	}
	defer c.Close()

	args := filters.NewArgs()
	args.Add("label", RKEContainerNameLabel+"="+name)
	// to handle upgrades of old container
	oldArgs := filters.NewArgs()
	oldArgs.Add("label", CattleProcessNameLabel+"="+name)

	containers, err := c.ContainerList(ctx, types.ContainerListOptions{
		All:     true,
		Filters: args,
	})
	if err != nil {
		return err
	}
	oldContainers, err := c.ContainerList(ctx, types.ContainerListOptions{
		All:     true,
		Filters: oldArgs,
	})
	if err != nil {
		return err
	}
	containers = append(containers, oldContainers...)
	var matchedContainers []types.Container
	for _, container := range containers {
		changed, err := changed(ctx, c, p, container)
		if err != nil {
			return err
		}

		if changed {
			err := remove(ctx, c, container.ID)
			if err != nil {
				return err
			}
		} else {
			matchedContainers = append(matchedContainers, container)
		}
	}

	for i := 1; i < len(matchedContainers); i++ {
		if err := remove(ctx, c, matchedContainers[i].ID); err != nil {
			return err
		}
	}

	if len(matchedContainers) > 0 {
		if strings.Contains(name, "share-mnt") {
			inspect, err := c.ContainerInspect(ctx, matchedContainers[0].ID)
			if err != nil {
				return err
			}
			if inspect.State != nil && inspect.State.Status == "exited" && inspect.State.ExitCode == 0 {
				return nil
			}
		}
		c.ContainerStart(ctx, matchedContainers[0].ID, types.ContainerStartOptions{})
		if !strings.Contains(name, "share-mnt") {
			runLogLinker(ctx, c, name, p)
		}
		return nil
	}

	config, hostConfig, _ := services.GetProcessConfig(p)
	if config.Labels == nil {
		config.Labels = map[string]string{}
	}
	config.Labels[RKEContainerNameLabel] = name

	newContainer, err := c.ContainerCreate(ctx, config, hostConfig, nil, name)
	if client.IsErrImageNotFound(err) {
		var output io.ReadCloser
		imagePullOptions := types.ImagePullOptions{}
		if p.ImageRegistryAuthConfig != "" {
			imagePullOptions.RegistryAuth = p.ImageRegistryAuthConfig
			imagePullOptions.PrivilegeFunc = func() (string, error) { return p.ImageRegistryAuthConfig, nil }
		}
		output, err = c.ImagePull(ctx, config.Image, imagePullOptions)
		if err != nil {
			return err
		}
		defer output.Close()
		io.Copy(logrus.StandardLogger().Writer(), output)
		newContainer, err = c.ContainerCreate(ctx, config, hostConfig, nil, name)
	}
	if err == nil && start {
		if err := c.ContainerStart(ctx, newContainer.ID, types.ContainerStartOptions{}); err != nil {
			return err
		}
		if !strings.Contains(name, "share-mnt") {
			return runLogLinker(ctx, c, name, p)
		}
		return nil
	}
	return err
}

func remove(ctx context.Context, c *client.Client, id string) error {
	return c.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
		Force:         true,
		RemoveVolumes: true,
	})
}

func changed(ctx context.Context, c *client.Client, expectedProcess v3.Process, container types.Container) (bool, error) {
	actualDockerInspect, err := c.ContainerInspect(ctx, container.ID)
	if err != nil {
		return false, err
	}
	defaultDockerInspect, _, err := c.ImageInspectWithRaw(ctx, actualDockerInspect.Image)
	if err != nil {
		return false, err
	}
	actualProcess := v3.Process{
		Command:     actualDockerInspect.Config.Entrypoint,
		Args:        actualDockerInspect.Config.Cmd,
		Env:         actualDockerInspect.Config.Env,
		Image:       actualDockerInspect.Config.Image,
		Binds:       actualDockerInspect.HostConfig.Binds,
		NetworkMode: string(actualDockerInspect.HostConfig.NetworkMode),
		PidMode:     string(actualDockerInspect.HostConfig.PidMode),
		Privileged:  actualDockerInspect.HostConfig.Privileged,
		VolumesFrom: actualDockerInspect.HostConfig.VolumesFrom,
		Labels:      actualDockerInspect.Config.Labels,
	}

	if len(expectedProcess.Command) == 0 {
		expectedProcess.Command = actualProcess.Command
	}
	if len(expectedProcess.Args) == 0 {
		expectedProcess.Args = actualProcess.Args
	}
	if len(expectedProcess.Env) == 0 {
		expectedProcess.Env = actualProcess.Env
	}
	if expectedProcess.NetworkMode == "" {
		expectedProcess.NetworkMode = actualProcess.NetworkMode
	}
	if expectedProcess.PidMode == "" {
		expectedProcess.PidMode = actualProcess.PidMode
	}
	if len(expectedProcess.Labels) == 0 {
		expectedProcess.Labels = actualProcess.Labels
	}

	// Don't detect changes on these fields
	actualProcess.Name = expectedProcess.Name
	actualProcess.HealthCheck.URL = expectedProcess.HealthCheck.URL
	actualProcess.RestartPolicy = expectedProcess.RestartPolicy

	changed := false
	t := reflect.TypeOf(actualProcess)
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		if f.Name == "Command" {
			leftMap := sliceToMap(expectedProcess.Command)
			rightMap := sliceToMap(actualProcess.Command)

			if reflect.DeepEqual(leftMap, rightMap) {
				continue
			}
		} else if f.Name == "Env" {
			expectedEnvs := make(map[string]string, 8)
			for _, env := range defaultDockerInspect.Config.Env {
				es := strings.SplitN(env, "=", 2)
				expectedEnvs[es[0]] = es[1]
			}
			for _, env := range expectedProcess.Env {
				es := strings.SplitN(env, "=", 2)
				expectedEnvs[es[0]] = es[1]
			}

			actualEnvs := make(map[string]string, 8)
			for _, env := range actualProcess.Env {
				es := strings.SplitN(env, "=", 2)
				actualEnvs[es[0]] = es[1]
			}

			if reflect.DeepEqual(expectedEnvs, actualEnvs) {
				continue
			}
		} else if f.Name == "Labels" {
			processLabels := make(map[string]string)
			for k, v := range defaultDockerInspect.Config.Labels {
				processLabels[k] = v
			}
			for k, v := range expectedProcess.Labels {
				processLabels[k] = v
			}

			if reflect.DeepEqual(processLabels, actualProcess.Labels) {
				continue
			}
		} else if f.Name == "Publish" {
			expectedExposedPortSet, expectedBindings, err := nat.ParsePortSpecs(reflect.ValueOf(expectedProcess).Field(i).Interface().([]string))
			if err != nil {
				return false, err
			}
			expectedExposedPorts := natPortSetToSlice(expectedExposedPortSet)
			nat.SortPortMap(expectedExposedPorts, expectedBindings)

			actualExposedPortSet := make(map[nat.Port]struct{}, 0)
			actualBindings := actualDockerInspect.HostConfig.PortBindings
			for port := range actualBindings {
				if _, exists := actualExposedPortSet[port]; !exists {
					actualExposedPortSet[port] = struct{}{}
				}
			}
			actualExposedPorts := natPortSetToSlice(actualExposedPortSet)
			nat.SortPortMap(actualExposedPorts, actualBindings)

			if reflect.DeepEqual(actualBindings, nat.PortMap(expectedBindings)) {
				continue
			}
		}

		left := reflect.ValueOf(actualProcess).Field(i).Interface()
		right := reflect.ValueOf(expectedProcess).Field(i).Interface()
		if !reflect.DeepEqual(left, right) {
			logrus.Infof("For process %s, %s has changed from %v to %v", expectedProcess.Name, f.Name, right, left)
			changed = true
		}
	}

	return changed, nil
}

func sliceToMap(args []string) map[string]bool {
	result := map[string]bool{}
	for _, arg := range args {
		result[arg] = true
	}
	return result
}

func natPortSetToSlice(args map[nat.Port]struct{}) []nat.Port {
	result := make([]nat.Port, 0, len(args))
	for arg := range args {
		result = append(result, arg)
	}
	return result
}

func runLogLinker(ctx context.Context, c *client.Client, containerName string, p v3.Process) error {
	inspect, err := c.ContainerInspect(ctx, containerName)
	if err != nil {
		return err
	}
	containerID := inspect.ID
	containerLogPath := inspect.LogPath
	containerLogLink := fmt.Sprintf("%s/%s_%s.log", hosts.RKELogsPath, containerName, containerID)
	logLinkerName := fmt.Sprintf("%s-%s", services.LogLinkContainerName, containerName)
	config := &container.Config{
		Image: p.Image,
		Tty:   true,
		Entrypoint: []string{
			"sh",
			"-c",
			fmt.Sprintf("mkdir -p %s ; ln -s %s %s", hosts.RKELogsPath, containerLogPath, containerLogLink),
		},
	}
	hostConfig := &container.HostConfig{
		Binds: []string{
			"/var/lib:/var/lib",
		},
		Privileged: true,
	}
	// remove log linker if its already exists
	remove(ctx, c, logLinkerName)

	newContainer, err := c.ContainerCreate(ctx, config, hostConfig, nil, logLinkerName)
	if err != nil {
		return err
	}
	if err := c.ContainerStart(ctx, newContainer.ID, types.ContainerStartOptions{}); err != nil {
		return err
	}
	// remove log linker after start
	return remove(ctx, c, logLinkerName)
}
<|endoftext|>