<commit_before>// Copyright 2020 Google LLC
//
// 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 middleware defines shared middleware for handlers.
package middleware

import (
	"context"
	"errors"
	"net/http"
	"time"

	"github.com/google/exposure-notifications-verification-server/pkg/controller"
	"github.com/google/exposure-notifications-verification-server/pkg/database"
	"github.com/google/exposure-notifications-verification-server/pkg/render"

	"github.com/google/exposure-notifications-server/pkg/logging"

	"firebase.google.com/go/auth"
	"github.com/gorilla/mux"
	"github.com/jinzhu/gorm"
)

// RequireAuth requires a user to be logged in. It also ensures that currentUser
// is set in the template map. It fetches a user from the session and stores the
// full record in the request context.
func RequireAuth(ctx context.Context, client *auth.Client, db *database.Database, h *render.Renderer, ttl time.Duration) mux.MiddlewareFunc {
	logger := logging.FromContext(ctx).Named("middleware.RequireAuth")

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ctx := r.Context()

			session := controller.SessionFromContext(ctx)
			if session == nil {
				logger.Errorw("session does not exist")
				controller.MissingSession(w, r, h)
				return
			}

			flash := controller.Flash(session)

			firebaseCookie := controller.FirebaseCookieFromSession(session)
			if firebaseCookie == "" {
				logger.Debugw("firebase cookie not in session")
				flash.Error("An error occurred trying to verify your credentials.")
				controller.Unauthorized(w, r, h)
				return
			}

			token, err := client.VerifySessionCookie(ctx, firebaseCookie)
			if err != nil {
				logger.Debugw("failed to verify firebase cookie", "error", err)
				flash.Error("An error occurred trying to verify your credentials.")
				controller.ClearSessionFirebaseCookie(session)
				controller.Unauthorized(w, r, h)
				return
			}

			emailRaw, ok := token.Claims["email"]
			if !ok {
				logger.Debugw("firebase token does not have an email")
				flash.Error("An error occurred trying to verify your credentials.")
				controller.ClearSessionFirebaseCookie(session)
				controller.Unauthorized(w, r, h)
				return
			}

			email, ok := emailRaw.(string)
			if !ok {
				logger.Debugw("firebase email is not a string")
				flash.Error("An error occurred trying to verify your credentials.")
				controller.ClearSessionFirebaseCookie(session)
				controller.Unauthorized(w, r, h)
				return
			}

			user, err := db.FindUserByEmail(email)
			if err != nil {
				if errors.Is(err, gorm.ErrRecordNotFound) {
					logger.Debugw("user does not exist")
					flash.Error("That user does not exist.")
					controller.Unauthorized(w, r, h)
					return
				}

				logger.Errorw("failed to find user", "error", err)
				controller.InternalError(w, r, h, err)
				return
			}

			if user == nil {
				logger.Debugw("user does not exist")
				controller.Unauthorized(w, r, h)
				return
			}

			// Check if the session is still valid.
			if time.Now().After(user.LastRevokeCheck.Add(ttl)) {
				if _, err := client.VerifySessionCookieAndCheckRevoked(ctx, firebaseCookie); err != nil {
					logger.Debugw("failed to verify firebase cookie revocation", "error", err)
					controller.ClearSessionFirebaseCookie(session)
					controller.Unauthorized(w, r, h)
					return
				}

				user.LastRevokeCheck = time.Now()
				if err := db.SaveUser(user); err != nil {
					logger.Errorw("failed to update revocation check time", "error", err)
					controller.InternalError(w, r, h, err)
					return
				}
			}

			// Save the user in the template map.
			m := controller.TemplateMapFromContext(ctx)
			m["currentUser"] = user

			// Save the user on the context.
			ctx = controller.WithUser(ctx, user)
			*r = *r.WithContext(ctx)

			next.ServeHTTP(w, r)
		})
	}
}

// RequireAdmin requires the current user is a global administrator. It must
// come after RequireAuth so that a user is set on the context.
func RequireAdmin(ctx context.Context, h *render.Renderer) mux.MiddlewareFunc {
	logger := logging.FromContext(ctx).Named("middleware.RequireAdminHandler")

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ctx := r.Context()

			user := controller.UserFromContext(ctx)
			if user == nil {
				controller.MissingUser(w, r, h)
				return
			}

			if !user.Admin {
				logger.Debugw("user is not an admin")
				controller.Unauthorized(w, r, h)
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}
<commit_msg>Fix redirect loop with valid Firebase user, but non-database user (#252)<commit_after>// Copyright 2020 Google LLC
//
// 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 middleware defines shared middleware for handlers.
package middleware

import (
	"context"
	"errors"
	"net/http"
	"time"

	"github.com/google/exposure-notifications-verification-server/pkg/controller"
	"github.com/google/exposure-notifications-verification-server/pkg/database"
	"github.com/google/exposure-notifications-verification-server/pkg/render"

	"github.com/google/exposure-notifications-server/pkg/logging"

	"firebase.google.com/go/auth"
	"github.com/gorilla/mux"
	"github.com/jinzhu/gorm"
)

// RequireAuth requires a user to be logged in. It also ensures that currentUser
// is set in the template map. It fetches a user from the session and stores the
// full record in the request context.
func RequireAuth(ctx context.Context, client *auth.Client, db *database.Database, h *render.Renderer, ttl time.Duration) mux.MiddlewareFunc {
	logger := logging.FromContext(ctx).Named("middleware.RequireAuth")

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ctx := r.Context()

			session := controller.SessionFromContext(ctx)
			if session == nil {
				logger.Errorw("session does not exist")
				controller.MissingSession(w, r, h)
				return
			}

			flash := controller.Flash(session)

			firebaseCookie := controller.FirebaseCookieFromSession(session)
			if firebaseCookie == "" {
				logger.Debugw("firebase cookie not in session")
				flash.Error("An error occurred trying to verify your credentials.")
				controller.Unauthorized(w, r, h)
				return
			}

			token, err := client.VerifySessionCookie(ctx, firebaseCookie)
			if err != nil {
				logger.Debugw("failed to verify firebase cookie", "error", err)
				flash.Error("An error occurred trying to verify your credentials.")
				controller.ClearSessionFirebaseCookie(session)
				controller.Unauthorized(w, r, h)
				return
			}

			emailRaw, ok := token.Claims["email"]
			if !ok {
				logger.Debugw("firebase token does not have an email")
				flash.Error("An error occurred trying to verify your credentials.")
				controller.ClearSessionFirebaseCookie(session)
				controller.Unauthorized(w, r, h)
				return
			}

			email, ok := emailRaw.(string)
			if !ok {
				logger.Debugw("firebase email is not a string")
				flash.Error("An error occurred trying to verify your credentials.")
				controller.ClearSessionFirebaseCookie(session)
				controller.Unauthorized(w, r, h)
				return
			}

			user, err := db.FindUserByEmail(email)
			if err != nil {
				if errors.Is(err, gorm.ErrRecordNotFound) {
					logger.Debugw("user does not exist")
					flash.Error("That user does not exist.")
					controller.ClearSessionFirebaseCookie(session)
					controller.Unauthorized(w, r, h)
					return
				}

				logger.Errorw("failed to find user", "error", err)
				controller.ClearSessionFirebaseCookie(session)
				controller.InternalError(w, r, h, err)
				return
			}

			if user == nil {
				logger.Debugw("user does not exist")
				controller.ClearSessionFirebaseCookie(session)
				controller.Unauthorized(w, r, h)
				return
			}

			// Check if the session is still valid.
			if time.Now().After(user.LastRevokeCheck.Add(ttl)) {
				if _, err := client.VerifySessionCookieAndCheckRevoked(ctx, firebaseCookie); err != nil {
					logger.Debugw("failed to verify firebase cookie revocation", "error", err)
					controller.ClearSessionFirebaseCookie(session)
					controller.Unauthorized(w, r, h)
					return
				}

				user.LastRevokeCheck = time.Now()
				if err := db.SaveUser(user); err != nil {
					logger.Errorw("failed to update revocation check time", "error", err)
					controller.InternalError(w, r, h, err)
					return
				}
			}

			// Save the user in the template map.
			m := controller.TemplateMapFromContext(ctx)
			m["currentUser"] = user

			// Save the user on the context.
			ctx = controller.WithUser(ctx, user)
			*r = *r.WithContext(ctx)

			next.ServeHTTP(w, r)
		})
	}
}

// RequireAdmin requires the current user is a global administrator. It must
// come after RequireAuth so that a user is set on the context.
func RequireAdmin(ctx context.Context, h *render.Renderer) mux.MiddlewareFunc {
	logger := logging.FromContext(ctx).Named("middleware.RequireAdminHandler")

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ctx := r.Context()

			user := controller.UserFromContext(ctx)
			if user == nil {
				controller.MissingUser(w, r, h)
				return
			}

			if !user.Admin {
				logger.Debugw("user is not an admin")
				controller.Unauthorized(w, r, h)
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// 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 gcv

import (
	"context"
	"sync"
	"testing"

	"github.com/pkg/errors"

	"github.com/forseti-security/config-validator/pkg/api/validator"
)

type reviewTestcase struct {
	name        string
	workerCount int
	calls       []reviewCall
}

type reviewCall struct {
	assets             []*validator.Asset // assets to use if not using the default asset set
	scaleFactor        int                // number of copies of asset list to put in one call to Review.
	wantViolationCount int                // the total violation count
	wantError          bool
}

type FakeConfigValidator struct {
	violationMap map[string][]*validator.Violation
}

func NewFakeConfigValidator(violationMap map[string][]*validator.Violation) *FakeConfigValidator {
	for name, violations := range violationMap {
		for _, v := range violations {
			v.Resource = name
		}
	}
	return &FakeConfigValidator{violationMap: violationMap}
}

func (v *FakeConfigValidator) ReviewAsset(ctx context.Context, asset *validator.Asset) ([]*validator.Violation, error) {
	violations, found := v.violationMap[asset.Name]
	if !found {
		return nil, errors.Errorf("name %s not found", asset.Name)
	}
	return violations, nil
}

func TestReview(t *testing.T) {
	// we will run 3x this amount of assets through audit, then reset at end
	// of test.
	var testCases = []reviewTestcase{
		{
			name:        "no assets",
			workerCount: 1,
			calls: []reviewCall{
				{
					assets: []*validator.Asset{},
				},
			},
		},
		{
			name:        "error",
			workerCount: 1,
			calls: []reviewCall{
				{
					assets:    []*validator.Asset{{Name: "invalid name"}},
					wantError: true,
				},
			},
		},
		{
			name:        "single call",
			workerCount: 1,
			calls: []reviewCall{
				{
					assets:             []*validator.Asset{storageAssetNoLogging()},
					wantViolationCount: 2,
				},
			},
		},
		{
			name:        "single call three assets",
			workerCount: 1,
			calls: []reviewCall{
				{
					assets:             defaultReviewTestAssets,
					wantViolationCount: 3,
				},
			},
		},
	}

	var testCase *reviewTestcase
	testCase = &reviewTestcase{
		name:        "128 goroutines x32 calls x16 scale",
		workerCount: 128,
	}
	for i := 0; i < 32; i++ {
		testCase.calls = append(
			testCase.calls,
			reviewCall{
				assets:             defaultReviewTestAssets,
				scaleFactor:        16,
				wantViolationCount: 3,
			},
		)
	}
	testCases = append(testCases, *testCase)
	testCase = &reviewTestcase{
		name:        "single call large scale deadlock test",
		workerCount: 4,
		calls: []reviewCall{
			{
				assets:             defaultReviewTestAssets,
				scaleFactor:        4 * 16,
				wantViolationCount: 3,
			},
		},
	}
	testCases = append(testCases, *testCase)

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			oldWorkerCount := flags.workerCount
			defer func() {
				flags.workerCount = oldWorkerCount
			}()
			flags.workerCount = tc.workerCount

			stopChannel := make(chan struct{})
			defer close(stopChannel)
			cv := NewFakeConfigValidator(
				map[string][]*validator.Violation{
					"//storage.googleapis.com/my-storage-bucket-with-logging":        nil,
					"//storage.googleapis.com/my-storage-bucket-with-secure-logging": nil,
					"//container.googleapis.com/projects/malaise-forever/zones/us-central1-a/clusters/test-1/k8s/namespaces/whatever": {
						{
							Constraint: "namespace-cost-center-label",
							Message:    "you must provide labels: {\"cost-center\"}",
						},
					},
					"//storage.googleapis.com/my-storage-bucket": {
						{
							Constraint: "require-storage-logging",
							Message:    "//storage.googleapis.com/my-storage-bucket does not have the required logging destination.",
						},
						{
							Constraint: "require-storage-logging-xx",
							Message:    "//storage.googleapis.com/my-storage-bucket does not have the required logging destination.",
						},
					},
				},
			)
			v := NewParallelValidator(stopChannel, cv)

			var groupDone sync.WaitGroup
			for callIdx, call := range tc.calls {
				groupDone.Add(1)
				go func(cIdx int, call reviewCall) {
					defer groupDone.Done()
					if call.scaleFactor == 0 {
						call.scaleFactor = 1
					}

					var assets []*validator.Asset
					for i := 0; i < call.scaleFactor; i++ {
						assets = append(assets, call.assets...)
					}

					result, err := v.Review(context.Background(), &validator.ReviewRequest{
						Assets: assets,
					})
					if call.wantError {
						if err == nil {
							t.Fatal("Expected error, got none")
						}
						return
					} else {
						if err != nil {
							t.Fatalf("review error in call %d: %s", cIdx, err)
						}
					}

					wantViolationCount := call.wantViolationCount * call.scaleFactor
					if len(result.Violations) != wantViolationCount {
						t.Fatalf("wanted %d violations, got %d", wantViolationCount, len(result.Violations))
					}
				}(callIdx, call)
			}
			groupDone.Wait()
		})
	}
}
<commit_msg>Stop calling t.Fatal in goroutine<commit_after>// Copyright 2019 Google LLC
//
// 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 gcv

import (
	"context"
	"sync"
	"testing"

	"github.com/pkg/errors"

	"github.com/forseti-security/config-validator/pkg/api/validator"
)

type reviewTestcase struct {
	name        string
	workerCount int
	calls       []reviewCall
}

type reviewCall struct {
	assets             []*validator.Asset // assets to use if not using the default asset set
	scaleFactor        int                // number of copies of asset list to put in one call to Review.
	wantViolationCount int                // the total violation count
	wantError          bool
}

type FakeConfigValidator struct {
	violationMap map[string][]*validator.Violation
}

func NewFakeConfigValidator(violationMap map[string][]*validator.Violation) *FakeConfigValidator {
	for name, violations := range violationMap {
		for _, v := range violations {
			v.Resource = name
		}
	}
	return &FakeConfigValidator{violationMap: violationMap}
}

func (v *FakeConfigValidator) ReviewAsset(ctx context.Context, asset *validator.Asset) ([]*validator.Violation, error) {
	violations, found := v.violationMap[asset.Name]
	if !found {
		return nil, errors.Errorf("name %s not found", asset.Name)
	}
	return violations, nil
}

func TestReview(t *testing.T) {
	// we will run 3x this amount of assets through audit, then reset at end
	// of test.
	var testCases = []reviewTestcase{
		{
			name:        "no assets",
			workerCount: 1,
			calls: []reviewCall{
				{
					assets: []*validator.Asset{},
				},
			},
		},
		{
			name:        "error",
			workerCount: 1,
			calls: []reviewCall{
				{
					assets:    []*validator.Asset{{Name: "invalid name"}},
					wantError: true,
				},
			},
		},
		{
			name:        "single call",
			workerCount: 1,
			calls: []reviewCall{
				{
					assets:             []*validator.Asset{storageAssetNoLogging()},
					wantViolationCount: 2,
				},
			},
		},
		{
			name:        "single call three assets",
			workerCount: 1,
			calls: []reviewCall{
				{
					assets:             defaultReviewTestAssets,
					wantViolationCount: 3,
				},
			},
		},
	}

	var testCase *reviewTestcase
	testCase = &reviewTestcase{
		name:        "128 goroutines x32 calls x16 scale",
		workerCount: 128,
	}
	for i := 0; i < 32; i++ {
		testCase.calls = append(
			testCase.calls,
			reviewCall{
				assets:             defaultReviewTestAssets,
				scaleFactor:        16,
				wantViolationCount: 3,
			},
		)
	}
	testCases = append(testCases, *testCase)
	testCase = &reviewTestcase{
		name:        "single call large scale deadlock test",
		workerCount: 4,
		calls: []reviewCall{
			{
				assets:             defaultReviewTestAssets,
				scaleFactor:        4 * 16,
				wantViolationCount: 3,
			},
		},
	}
	testCases = append(testCases, *testCase)

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			oldWorkerCount := flags.workerCount
			defer func() {
				flags.workerCount = oldWorkerCount
			}()
			flags.workerCount = tc.workerCount

			stopChannel := make(chan struct{})
			defer close(stopChannel)
			cv := NewFakeConfigValidator(
				map[string][]*validator.Violation{
					"//storage.googleapis.com/my-storage-bucket-with-logging":        nil,
					"//storage.googleapis.com/my-storage-bucket-with-secure-logging": nil,
					"//container.googleapis.com/projects/malaise-forever/zones/us-central1-a/clusters/test-1/k8s/namespaces/whatever": {
						{
							Constraint: "namespace-cost-center-label",
							Message:    "you must provide labels: {\"cost-center\"}",
						},
					},
					"//storage.googleapis.com/my-storage-bucket": {
						{
							Constraint: "require-storage-logging",
							Message:    "//storage.googleapis.com/my-storage-bucket does not have the required logging destination.",
						},
						{
							Constraint: "require-storage-logging-xx",
							Message:    "//storage.googleapis.com/my-storage-bucket does not have the required logging destination.",
						},
					},
				},
			)
			v := NewParallelValidator(stopChannel, cv)

			var groupDone sync.WaitGroup
			for callIdx, call := range tc.calls {
				groupDone.Add(1)
				go func(cIdx int, call reviewCall) {
					defer groupDone.Done()
					if call.scaleFactor == 0 {
						call.scaleFactor = 1
					}

					var assets []*validator.Asset
					for i := 0; i < call.scaleFactor; i++ {
						assets = append(assets, call.assets...)
					}

					result, err := v.Review(context.Background(), &validator.ReviewRequest{
						Assets: assets,
					})
					if call.wantError {
						if err == nil {
							t.Errorf("Expected error, got none")
							return
						}
						return
					} else {
						if err != nil {
							t.Errorf("review error in call %d: %s", cIdx, err)
							return
						}
					}

					wantViolationCount := call.wantViolationCount * call.scaleFactor
					if len(result.Violations) != wantViolationCount {
						t.Errorf("wanted %d violations, got %d", wantViolationCount, len(result.Violations))
					}
				}(callIdx, call)
			}
			groupDone.Wait()
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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 dockertools

import (
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"

	"github.com/docker/docker/pkg/jsonmessage"
	"github.com/docker/docker/pkg/parsers"
	docker "github.com/fsouza/go-dockerclient"
	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/credentialprovider"
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
	"k8s.io/kubernetes/pkg/kubelet/leaky"
	kubeletTypes "k8s.io/kubernetes/pkg/kubelet/types"
	"k8s.io/kubernetes/pkg/types"
	"k8s.io/kubernetes/pkg/util"
	utilerrors "k8s.io/kubernetes/pkg/util/errors"
)

const (
	PodInfraContainerName  = leaky.PodInfraContainerName
	DockerPrefix           = "docker://"
	PodInfraContainerImage = "gcr.io/google_containers/pause:0.8.0"
	LogSuffix              = "log"
)

const (
	// Taken from lmctfy https://github.com/google/lmctfy/blob/master/lmctfy/controllers/cpu_controller.cc
	minShares     = 2
	sharesPerCPU  = 1024
	milliCPUToCPU = 1000
)

// DockerInterface is an abstract interface for testability.  It abstracts the interface of docker.Client.
type DockerInterface interface {
	ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error)
	InspectContainer(id string) (*docker.Container, error)
	CreateContainer(docker.CreateContainerOptions) (*docker.Container, error)
	StartContainer(id string, hostConfig *docker.HostConfig) error
	StopContainer(id string, timeout uint) error
	RemoveContainer(opts docker.RemoveContainerOptions) error
	InspectImage(image string) (*docker.Image, error)
	ListImages(opts docker.ListImagesOptions) ([]docker.APIImages, error)
	PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
	RemoveImage(image string) error
	Logs(opts docker.LogsOptions) error
	Version() (*docker.Env, error)
	Info() (*docker.Env, error)
	CreateExec(docker.CreateExecOptions) (*docker.Exec, error)
	StartExec(string, docker.StartExecOptions) error
	InspectExec(id string) (*docker.ExecInspect, error)
	AttachToContainer(opts docker.AttachToContainerOptions) error
}

// KubeletContainerName encapsulates a pod name and a Kubernetes container name.
type KubeletContainerName struct {
	PodFullName   string
	PodUID        types.UID
	ContainerName string
}

// DockerPuller is an abstract interface for testability.  It abstracts image pull operations.
type DockerPuller interface {
	Pull(image string, secrets []api.Secret) error
	IsImagePresent(image string) (bool, error)
}

// dockerPuller is the default implementation of DockerPuller.
type dockerPuller struct {
	client  DockerInterface
	keyring credentialprovider.DockerKeyring
}

type throttledDockerPuller struct {
	puller  dockerPuller
	limiter util.RateLimiter
}

// newDockerPuller creates a new instance of the default implementation of DockerPuller.
func newDockerPuller(client DockerInterface, qps float32, burst int) DockerPuller {
	dp := dockerPuller{
		client:  client,
		keyring: credentialprovider.NewDockerKeyring(),
	}

	if qps == 0.0 {
		return dp
	}
	return &throttledDockerPuller{
		puller:  dp,
		limiter: util.NewTokenBucketRateLimiter(qps, burst),
	}
}

func parseImageName(image string) (string, string) {
	return parsers.ParseRepositoryTag(image)
}

func filterHTTPError(err error, image string) error {
	// docker/docker/pull/11314 prints detailed error info for docker pull.
	// When it hits 502, it returns a verbose html output including an inline svg,
	// which makes the output of kubectl get pods much harder to parse.
	// Here converts such verbose output to a concise one.
	jerr, ok := err.(*jsonmessage.JSONError)
	if ok && (jerr.Code == http.StatusBadGateway ||
		jerr.Code == http.StatusServiceUnavailable ||
		jerr.Code == http.StatusGatewayTimeout) {
		glog.V(2).Infof("Pulling image %q failed: %v", image, err)
		return fmt.Errorf("image pull failed for %s because the registry is temporarily unavailbe.", image)
	} else {
		return err
	}
}

func (p dockerPuller) Pull(image string, secrets []api.Secret) error {
	repoToPull, tag := parseImageName(image)

	// If no tag was specified, use the default "latest".
	if len(tag) == 0 {
		tag = "latest"
	}

	opts := docker.PullImageOptions{
		Repository: repoToPull,
		Tag:        tag,
	}

	keyring, err := credentialprovider.MakeDockerKeyring(secrets, p.keyring)
	if err != nil {
		return err
	}

	creds, haveCredentials := keyring.Lookup(repoToPull)
	if !haveCredentials {
		glog.V(1).Infof("Pulling image %s without credentials", image)

		err := p.client.PullImage(opts, docker.AuthConfiguration{})
		if err == nil {
			return nil
		}

		// Image spec: [<registry>/]<repository>/<image>[:<version] so we count '/'
		explicitRegistry := (strings.Count(image, "/") == 2)
		// Hack, look for a private registry, and decorate the error with the lack of
		// credentials.  This is heuristic, and really probably could be done better
		// by talking to the registry API directly from the kubelet here.
		if explicitRegistry {
			return fmt.Errorf("image pull failed for %s, this may be because there are no credentials on this request.  details: (%v)", image, err)
		}

		return filterHTTPError(err, image)
	}

	var pullErrs []error
	for _, currentCreds := range creds {
		err := p.client.PullImage(opts, currentCreds)
		// If there was no error, return success
		if err == nil {
			return nil
		}

		pullErrs = append(pullErrs, filterHTTPError(err, image))
	}

	return utilerrors.NewAggregate(pullErrs)
}

func (p throttledDockerPuller) Pull(image string, secrets []api.Secret) error {
	if p.limiter.CanAccept() {
		return p.puller.Pull(image, secrets)
	}
	return fmt.Errorf("pull QPS exceeded.")
}

func (p dockerPuller) IsImagePresent(image string) (bool, error) {
	_, err := p.client.InspectImage(image)
	if err == nil {
		return true, nil
	}
	if err == docker.ErrNoSuchImage {
		return false, nil
	}
	return false, err
}

func (p throttledDockerPuller) IsImagePresent(name string) (bool, error) {
	return p.puller.IsImagePresent(name)
}

// DockerContainers is a map of containers
type DockerContainers map[kubeletTypes.DockerID]*docker.APIContainers

func (c DockerContainers) FindPodContainer(podFullName string, uid types.UID, containerName string) (*docker.APIContainers, bool, uint64) {
	for _, dockerContainer := range c {
		if len(dockerContainer.Names) == 0 {
			continue
		}
		// TODO(proppy): build the docker container name and do a map lookup instead?
		dockerName, hash, err := ParseDockerName(dockerContainer.Names[0])
		if err != nil {
			continue
		}
		if dockerName.PodFullName == podFullName &&
			(uid == "" || dockerName.PodUID == uid) &&
			dockerName.ContainerName == containerName {
			return dockerContainer, true, hash
		}
	}
	return nil, false, 0
}

const containerNamePrefix = "k8s"

// Creates a name which can be reversed to identify both full pod name and container name.
func BuildDockerName(dockerName KubeletContainerName, container *api.Container) string {
	containerName := dockerName.ContainerName + "." + strconv.FormatUint(kubecontainer.HashContainer(container), 16)
	return fmt.Sprintf("%s_%s_%s_%s_%08x",
		containerNamePrefix,
		containerName,
		dockerName.PodFullName,
		dockerName.PodUID,
		rand.Uint32())
}

// Unpacks a container name, returning the pod full name and container name we would have used to
// construct the docker name. If we are unable to parse the name, an error is returned.
func ParseDockerName(name string) (dockerName *KubeletContainerName, hash uint64, err error) {
	// For some reason docker appears to be appending '/' to names.
	// If it's there, strip it.
	name = strings.TrimPrefix(name, "/")
	parts := strings.Split(name, "_")
	if len(parts) == 0 || parts[0] != containerNamePrefix {
		err = fmt.Errorf("failed to parse Docker container name %q into parts", name)
		return nil, 0, err
	}
	if len(parts) < 6 {
		// We have at least 5 fields.  We may have more in the future.
		// Anything with less fields than this is not something we can
		// manage.
		glog.Warningf("found a container with the %q prefix, but too few fields (%d): %q", containerNamePrefix, len(parts), name)
		err = fmt.Errorf("Docker container name %q has less parts than expected %v", name, parts)
		return nil, 0, err
	}

	nameParts := strings.Split(parts[1], ".")
	containerName := nameParts[0]
	if len(nameParts) > 1 {
		hash, err = strconv.ParseUint(nameParts[1], 16, 32)
		if err != nil {
			glog.Warningf("invalid container hash %q in container %q", nameParts[1], name)
		}
	}

	podFullName := parts[2] + "_" + parts[3]
	podUID := types.UID(parts[4])

	return &KubeletContainerName{podFullName, podUID, containerName}, hash, nil
}

func LogSymlink(containerLogsDir, podFullName, containerName, dockerId string) string {
	return path.Join(containerLogsDir, fmt.Sprintf("%s_%s-%s.%s", podFullName, containerName, dockerId, LogSuffix))
}

// Get a docker endpoint, either from the string passed in, or $DOCKER_HOST environment variables
func getDockerEndpoint(dockerEndpoint string) string {
	var endpoint string
	if len(dockerEndpoint) > 0 {
		endpoint = dockerEndpoint
	} else if len(os.Getenv("DOCKER_HOST")) > 0 {
		endpoint = os.Getenv("DOCKER_HOST")
	} else {
		endpoint = "unix:///var/run/docker.sock"
	}
	glog.Infof("Connecting to docker on %s", endpoint)

	return endpoint
}

func ConnectToDockerOrDie(dockerEndpoint string) DockerInterface {
	if dockerEndpoint == "fake://" {
		return &FakeDockerClient{
			VersionInfo: docker.Env{"ApiVersion=1.18"},
		}
	}
	client, err := docker.NewClient(getDockerEndpoint(dockerEndpoint))
	if err != nil {
		glog.Fatalf("Couldn't connect to docker: %v", err)
	}
	return client
}

func milliCPUToShares(milliCPU int64) int64 {
	if milliCPU == 0 {
		// Docker converts zero milliCPU to unset, which maps to kernel default
		// for unset: 1024. Return 2 here to really match kernel default for
		// zero milliCPU.
		return minShares
	}
	// Conceptually (milliCPU / milliCPUToCPU) * sharesPerCPU, but factored to improve rounding.
	shares := (milliCPU * sharesPerCPU) / milliCPUToCPU
	if shares < minShares {
		return minShares
	}
	return shares
}

// GetKubeletDockerContainers lists all container or just the running ones.
// Returns a map of docker containers that we manage, keyed by container ID.
// TODO: Move this function with dockerCache to DockerManager.
func GetKubeletDockerContainers(client DockerInterface, allContainers bool) (DockerContainers, error) {
	result := make(DockerContainers)
	containers, err := client.ListContainers(docker.ListContainersOptions{All: allContainers})
	if err != nil {
		return nil, err
	}
	for i := range containers {
		container := &containers[i]
		if len(container.Names) == 0 {
			continue
		}
		// Skip containers that we didn't create to allow users to manually
		// spin up their own containers if they want.
		// TODO(dchen1107): Remove the old separator "--" by end of Oct
		if !strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"_") &&
			!strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"--") {
			glog.V(3).Infof("Docker Container: %s is not managed by kubelet.", container.Names[0])
			continue
		}
		result[kubeletTypes.DockerID(container.ID)] = container
	}
	return result, nil
}
<commit_msg>use docker.NewClientFromEnv for creation of docker.Client<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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 dockertools

import (
	"fmt"
	"math/rand"
	"net/http"
	"path"
	"strconv"
	"strings"

	"github.com/docker/docker/pkg/jsonmessage"
	"github.com/docker/docker/pkg/parsers"
	docker "github.com/fsouza/go-dockerclient"
	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/credentialprovider"
	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
	"k8s.io/kubernetes/pkg/kubelet/leaky"
	kubeletTypes "k8s.io/kubernetes/pkg/kubelet/types"
	"k8s.io/kubernetes/pkg/types"
	"k8s.io/kubernetes/pkg/util"
	utilerrors "k8s.io/kubernetes/pkg/util/errors"
)

const (
	PodInfraContainerName  = leaky.PodInfraContainerName
	DockerPrefix           = "docker://"
	PodInfraContainerImage = "gcr.io/google_containers/pause:0.8.0"
	LogSuffix              = "log"
)

const (
	// Taken from lmctfy https://github.com/google/lmctfy/blob/master/lmctfy/controllers/cpu_controller.cc
	minShares     = 2
	sharesPerCPU  = 1024
	milliCPUToCPU = 1000
)

// DockerInterface is an abstract interface for testability.  It abstracts the interface of docker.Client.
type DockerInterface interface {
	ListContainers(options docker.ListContainersOptions) ([]docker.APIContainers, error)
	InspectContainer(id string) (*docker.Container, error)
	CreateContainer(docker.CreateContainerOptions) (*docker.Container, error)
	StartContainer(id string, hostConfig *docker.HostConfig) error
	StopContainer(id string, timeout uint) error
	RemoveContainer(opts docker.RemoveContainerOptions) error
	InspectImage(image string) (*docker.Image, error)
	ListImages(opts docker.ListImagesOptions) ([]docker.APIImages, error)
	PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
	RemoveImage(image string) error
	Logs(opts docker.LogsOptions) error
	Version() (*docker.Env, error)
	Info() (*docker.Env, error)
	CreateExec(docker.CreateExecOptions) (*docker.Exec, error)
	StartExec(string, docker.StartExecOptions) error
	InspectExec(id string) (*docker.ExecInspect, error)
	AttachToContainer(opts docker.AttachToContainerOptions) error
}

// KubeletContainerName encapsulates a pod name and a Kubernetes container name.
type KubeletContainerName struct {
	PodFullName   string
	PodUID        types.UID
	ContainerName string
}

// DockerPuller is an abstract interface for testability.  It abstracts image pull operations.
type DockerPuller interface {
	Pull(image string, secrets []api.Secret) error
	IsImagePresent(image string) (bool, error)
}

// dockerPuller is the default implementation of DockerPuller.
type dockerPuller struct {
	client  DockerInterface
	keyring credentialprovider.DockerKeyring
}

type throttledDockerPuller struct {
	puller  dockerPuller
	limiter util.RateLimiter
}

// newDockerPuller creates a new instance of the default implementation of DockerPuller.
func newDockerPuller(client DockerInterface, qps float32, burst int) DockerPuller {
	dp := dockerPuller{
		client:  client,
		keyring: credentialprovider.NewDockerKeyring(),
	}

	if qps == 0.0 {
		return dp
	}
	return &throttledDockerPuller{
		puller:  dp,
		limiter: util.NewTokenBucketRateLimiter(qps, burst),
	}
}

func parseImageName(image string) (string, string) {
	return parsers.ParseRepositoryTag(image)
}

func filterHTTPError(err error, image string) error {
	// docker/docker/pull/11314 prints detailed error info for docker pull.
	// When it hits 502, it returns a verbose html output including an inline svg,
	// which makes the output of kubectl get pods much harder to parse.
	// Here converts such verbose output to a concise one.
	jerr, ok := err.(*jsonmessage.JSONError)
	if ok && (jerr.Code == http.StatusBadGateway ||
		jerr.Code == http.StatusServiceUnavailable ||
		jerr.Code == http.StatusGatewayTimeout) {
		glog.V(2).Infof("Pulling image %q failed: %v", image, err)
		return fmt.Errorf("image pull failed for %s because the registry is temporarily unavailbe.", image)
	} else {
		return err
	}
}

func (p dockerPuller) Pull(image string, secrets []api.Secret) error {
	repoToPull, tag := parseImageName(image)

	// If no tag was specified, use the default "latest".
	if len(tag) == 0 {
		tag = "latest"
	}

	opts := docker.PullImageOptions{
		Repository: repoToPull,
		Tag:        tag,
	}

	keyring, err := credentialprovider.MakeDockerKeyring(secrets, p.keyring)
	if err != nil {
		return err
	}

	creds, haveCredentials := keyring.Lookup(repoToPull)
	if !haveCredentials {
		glog.V(1).Infof("Pulling image %s without credentials", image)

		err := p.client.PullImage(opts, docker.AuthConfiguration{})
		if err == nil {
			return nil
		}

		// Image spec: [<registry>/]<repository>/<image>[:<version] so we count '/'
		explicitRegistry := (strings.Count(image, "/") == 2)
		// Hack, look for a private registry, and decorate the error with the lack of
		// credentials.  This is heuristic, and really probably could be done better
		// by talking to the registry API directly from the kubelet here.
		if explicitRegistry {
			return fmt.Errorf("image pull failed for %s, this may be because there are no credentials on this request.  details: (%v)", image, err)
		}

		return filterHTTPError(err, image)
	}

	var pullErrs []error
	for _, currentCreds := range creds {
		err := p.client.PullImage(opts, currentCreds)
		// If there was no error, return success
		if err == nil {
			return nil
		}

		pullErrs = append(pullErrs, filterHTTPError(err, image))
	}

	return utilerrors.NewAggregate(pullErrs)
}

func (p throttledDockerPuller) Pull(image string, secrets []api.Secret) error {
	if p.limiter.CanAccept() {
		return p.puller.Pull(image, secrets)
	}
	return fmt.Errorf("pull QPS exceeded.")
}

func (p dockerPuller) IsImagePresent(image string) (bool, error) {
	_, err := p.client.InspectImage(image)
	if err == nil {
		return true, nil
	}
	if err == docker.ErrNoSuchImage {
		return false, nil
	}
	return false, err
}

func (p throttledDockerPuller) IsImagePresent(name string) (bool, error) {
	return p.puller.IsImagePresent(name)
}

// DockerContainers is a map of containers
type DockerContainers map[kubeletTypes.DockerID]*docker.APIContainers

func (c DockerContainers) FindPodContainer(podFullName string, uid types.UID, containerName string) (*docker.APIContainers, bool, uint64) {
	for _, dockerContainer := range c {
		if len(dockerContainer.Names) == 0 {
			continue
		}
		// TODO(proppy): build the docker container name and do a map lookup instead?
		dockerName, hash, err := ParseDockerName(dockerContainer.Names[0])
		if err != nil {
			continue
		}
		if dockerName.PodFullName == podFullName &&
			(uid == "" || dockerName.PodUID == uid) &&
			dockerName.ContainerName == containerName {
			return dockerContainer, true, hash
		}
	}
	return nil, false, 0
}

const containerNamePrefix = "k8s"

// Creates a name which can be reversed to identify both full pod name and container name.
func BuildDockerName(dockerName KubeletContainerName, container *api.Container) string {
	containerName := dockerName.ContainerName + "." + strconv.FormatUint(kubecontainer.HashContainer(container), 16)
	return fmt.Sprintf("%s_%s_%s_%s_%08x",
		containerNamePrefix,
		containerName,
		dockerName.PodFullName,
		dockerName.PodUID,
		rand.Uint32())
}

// Unpacks a container name, returning the pod full name and container name we would have used to
// construct the docker name. If we are unable to parse the name, an error is returned.
func ParseDockerName(name string) (dockerName *KubeletContainerName, hash uint64, err error) {
	// For some reason docker appears to be appending '/' to names.
	// If it's there, strip it.
	name = strings.TrimPrefix(name, "/")
	parts := strings.Split(name, "_")
	if len(parts) == 0 || parts[0] != containerNamePrefix {
		err = fmt.Errorf("failed to parse Docker container name %q into parts", name)
		return nil, 0, err
	}
	if len(parts) < 6 {
		// We have at least 5 fields.  We may have more in the future.
		// Anything with less fields than this is not something we can
		// manage.
		glog.Warningf("found a container with the %q prefix, but too few fields (%d): %q", containerNamePrefix, len(parts), name)
		err = fmt.Errorf("Docker container name %q has less parts than expected %v", name, parts)
		return nil, 0, err
	}

	nameParts := strings.Split(parts[1], ".")
	containerName := nameParts[0]
	if len(nameParts) > 1 {
		hash, err = strconv.ParseUint(nameParts[1], 16, 32)
		if err != nil {
			glog.Warningf("invalid container hash %q in container %q", nameParts[1], name)
		}
	}

	podFullName := parts[2] + "_" + parts[3]
	podUID := types.UID(parts[4])

	return &KubeletContainerName{podFullName, podUID, containerName}, hash, nil
}

func LogSymlink(containerLogsDir, podFullName, containerName, dockerId string) string {
	return path.Join(containerLogsDir, fmt.Sprintf("%s_%s-%s.%s", podFullName, containerName, dockerId, LogSuffix))
}

// Get a *docker.Client, either using the endpoint passed in, or using
// DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT path per their spec
func getDockerClient(dockerEndpoint string) (*docker.Client, error) {
	if len(dockerEndpoint) > 0 {
		glog.Infof("Connecting to docker on %s", dockerEndpoint)
		return docker.NewClient(dockerEndpoint)
	}
	return docker.NewClientFromEnv()
}

func ConnectToDockerOrDie(dockerEndpoint string) DockerInterface {
	if dockerEndpoint == "fake://" {
		return &FakeDockerClient{
			VersionInfo: docker.Env{"ApiVersion=1.18"},
		}
	}
	client, err := getDockerClient(dockerEndpoint)
	if err != nil {
		glog.Fatalf("Couldn't connect to docker: %v", err)
	}
	return client
}

func milliCPUToShares(milliCPU int64) int64 {
	if milliCPU == 0 {
		// Docker converts zero milliCPU to unset, which maps to kernel default
		// for unset: 1024. Return 2 here to really match kernel default for
		// zero milliCPU.
		return minShares
	}
	// Conceptually (milliCPU / milliCPUToCPU) * sharesPerCPU, but factored to improve rounding.
	shares := (milliCPU * sharesPerCPU) / milliCPUToCPU
	if shares < minShares {
		return minShares
	}
	return shares
}

// GetKubeletDockerContainers lists all container or just the running ones.
// Returns a map of docker containers that we manage, keyed by container ID.
// TODO: Move this function with dockerCache to DockerManager.
func GetKubeletDockerContainers(client DockerInterface, allContainers bool) (DockerContainers, error) {
	result := make(DockerContainers)
	containers, err := client.ListContainers(docker.ListContainersOptions{All: allContainers})
	if err != nil {
		return nil, err
	}
	for i := range containers {
		container := &containers[i]
		if len(container.Names) == 0 {
			continue
		}
		// Skip containers that we didn't create to allow users to manually
		// spin up their own containers if they want.
		// TODO(dchen1107): Remove the old separator "--" by end of Oct
		if !strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"_") &&
			!strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"--") {
			glog.V(3).Infof("Docker Container: %s is not managed by kubelet.", container.Names[0])
			continue
		}
		result[kubeletTypes.DockerID(container.ID)] = container
	}
	return result, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The cert-manager 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 install

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/spf13/cobra"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/resource"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"

	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/chart"
	"helm.sh/helm/v3/pkg/cli"
	"helm.sh/helm/v3/pkg/cli/values"
	"helm.sh/helm/v3/pkg/release"

	helm "github.com/jetstack/cert-manager/cmd/ctl/pkg/install/helm"
)

const uninstallDesc = `
This command uninstalls cert-manager.

It can uninstall cert-manager even if it was installed by another install tool.

This command will also delete CRD resources when providing the '--remove-crds' flag.
It is safer to use this cli than using Helm directly (which might automatically remove
cert-manager crds when uninstalling, depending on the install parameters).

The tool tries to find a Helm-based cert-manager install (installed directly by Helm or
by this cli tool) and removes the resources based on the found Helm release.

Some example uses:
	$ kubectl cert-manager x uninstall
or
	$ kubectl cert-manager x uninstall --remove-crds
or
	$ kubectl cert-manager x uninstall -n new-cert-manager
`

type UninstallOptions struct {
	settings  *cli.EnvSettings
	cfg       *action.Configuration
	client    *action.Install
	valueOpts *values.Options

	ChartName  string
	RemoveCrds bool

	genericclioptions.IOStreams
}

func NewCmdUninstall(ctx context.Context, ioStreams genericclioptions.IOStreams, factory cmdutil.Factory) *cobra.Command {
	settings := cli.New()
	cfg := new(action.Configuration)

	options := &UninstallOptions{
		settings:  settings,
		cfg:       cfg,
		client:    action.NewInstall(cfg),
		valueOpts: &values.Options{},

		IOStreams: ioStreams,
	}

	// Set default namespace cli flag value
	defaults := make(map[string]string)
	defaults["namespace"] = "cert-manager"

	cmd := &cobra.Command{
		Use:   "uninstall",
		Short: "Uninstall cert-manager",
		Long:  uninstallDesc,
		RunE: func(cmd *cobra.Command, args []string) error {
			if err := helm.CopyCliFlags(cmd.Root().PersistentFlags(), defaults, settings); err != nil {
				return nil
			}
			options.client.Namespace = settings.Namespace()

			return options.runUninstall()
		},
		SilenceUsage:  true,
		SilenceErrors: true,
	}

	addInstallUninstallFlags(cmd.Flags(), &options.client.Timeout, &options.client.Wait)

	cmd.Flags().BoolVar(&options.RemoveCrds, "remove-crds", false, "Also remove crds")
	cmd.Flags().StringVar(&options.ChartName, "chart-name", "Cert-manager", "name of the chart to uninstall")

	return cmd
}

func (o *UninstallOptions) runUninstall() error {
	log.SetFlags(0)
	log.SetOutput(o.Out)

	if err := o.cfg.Init(o.settings.RESTClientGetter(), o.settings.Namespace(), os.Getenv("HELM_DRIVER"), log.Printf); err != nil {
		return err
	}

	// Find helm releases linked to a chart with the cert-manager ChartName
	certManagerReleases, err := o.cfg.Releases.List(func(rel *release.Release) bool {
		return rel.Chart.Name() == o.ChartName
	})
	if err != nil {
		return err
	}

	if len(certManagerReleases) > 1 {
		log.Printf(">> Found more than 1 cert-manager installation. Only one one of these installations will get uninstalled. Please rerun to also uninstall the other installations.")
	}

	if len(certManagerReleases) == 0 {
		return fmt.Errorf("No helm-based (installed via helm or the cert-manager kubectl plugin) installation was found.")
	}

	log.Printf(">> Found a helm-based installation, will use the original chart for removal.")
	releaseName, ch, chartValues, err := o.chartAndOptionsFromRelease(certManagerReleases[0])
	if err != nil {
		return err
	}
	o.client.ReleaseName = releaseName

	// Dryrun template generation (used for rendering the resources that should be deleted)
	o.client.DryRun = true                          // Do not apply install
	o.client.IsUpgrade = true                       // Do not validate against cluster
	chartValues[installCRDsFlagName] = o.RemoveCrds // Only render crds if cli flag is provided
	dryRunResult, err := o.client.Run(ch, chartValues)
	if err != nil {
		return err
	}

	// Extract all resources that are present in the chart
	resources, err := helm.GetChartResourceInfo(dryRunResult.Manifest, o.cfg.KubeClient)
	if err != nil {
		return err
	}

	installedResources, err := helm.FetchResources(resources, o.cfg.KubeClient)
	if err != nil {
		return err
	}

	namespacedResources := helm.FilterNamespacedResources(installedResources, o.settings.Namespace())
	crdResources := helm.FilterCrdResources(installedResources)

	// Only delete in case we have found resources in the current namespace OR the only resources found are crds.
	if len(namespacedResources) > 0 || (len(installedResources) > 0 && len(installedResources) == len(crdResources)) {
		// Remove the resources that were generated, by calling the KubeClient directly
		if _, err := o.cfg.KubeClient.Delete(resources); err != nil {
			return fmt.Errorf("failed to delete %s", err)
		}

		if o.client.Wait {
			if err := o.waitForDeletedResources(resources); err != nil {
				return err
			}
		}
	} else if len(installedResources) > 0 {
		// Resources linked to cert-manager were found, but none were found in the current namespace.
		return fmt.Errorf("Only found non-namespaced resources linked to cert-manager. Make sure \"--namespace\" flag is set correctly.")
	} else if !o.RemoveCrds {
		// No resources were found that were generated by a cert-manager installation.
		// But we did not check for crds, so maybe we want to rerun with the --remove-crds flag set?
		log.Printf("Found nothing to uninstall. If you want to remove crds, rerun with the \"--remove-crds\" flag set.")
	} else {
		// No resources were found that were generated by a cert-manager installation.
		log.Printf("Found nothing to uninstall.")
	}

	if len(certManagerReleases) > 0 {
		log.Printf(">> Everything was removed, also removing Helm entry.")
		return o.removeHelmReleaseAndHistory(certManagerReleases[0])
	}

	return nil
}

func (o *UninstallOptions) chartAndOptionsFromRelease(rel *release.Release) (string, *chart.Chart, map[string]interface{}, error) {
	// Overwrite the installCRDs flag so that crds are ONLY removed if the command flag is set
	rel.Config[installCRDsFlagName] = o.RemoveCrds

	return rel.Name, rel.Chart, rel.Config, nil
}

// For sake of simplicity, don't allow to keep history. Equivalent with not allowing
// --keep-hisory flag to be true (https://helm.sh/docs/helm/helm_uninstall/).
func (o *UninstallOptions) removeHelmReleaseAndHistory(rel *release.Release) error {
	rels, err := o.cfg.Releases.History(rel.Name)
	if err != nil {
		return err
	}

	for _, rel := range rels {
		if _, err := o.cfg.Releases.Delete(rel.Name, rel.Version); err != nil {
			return err
		}
	}
	return nil
}

// TODO: wait for uninstall should get merged into Helm (https://github.com/helm/helm/pull/9702)
// waitForDeletedResources polls to check if all the resources are deleted or a timeout is reached
func (o *UninstallOptions) waitForDeletedResources(deleted []*resource.Info) error {
	log.Printf("beginning wait for %d resources to be deleted with timeout of %v", len(deleted), o.client.Timeout)

	ctx, cancel := context.WithTimeout(context.Background(), o.client.Timeout)
	defer cancel()

	return wait.PollImmediateUntil(2*time.Second, func() (bool, error) {
		for _, v := range deleted {
			err := v.Get()
			if err == nil || !apierrors.IsNotFound(err) {
				return false, err
			}
		}
		return true, nil
	}, ctx.Done())
}
<commit_msg>bugfix rel.Config is nil<commit_after>/*
Copyright 2021 The cert-manager 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 install

import (
	"context"
	"fmt"
	"log"
	"os"
	"time"

	"github.com/spf13/cobra"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/resource"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"

	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/chart"
	"helm.sh/helm/v3/pkg/cli"
	"helm.sh/helm/v3/pkg/cli/values"
	"helm.sh/helm/v3/pkg/release"

	helm "github.com/jetstack/cert-manager/cmd/ctl/pkg/install/helm"
)

const uninstallDesc = `
This command uninstalls cert-manager.

It can uninstall cert-manager even if it was installed by another install tool.

This command will also delete CRD resources when providing the '--remove-crds' flag.
It is safer to use this cli than using Helm directly (which might automatically remove
cert-manager crds when uninstalling, depending on the install parameters).

The tool tries to find a Helm-based cert-manager install (installed directly by Helm or
by this cli tool) and removes the resources based on the found Helm release.

Some example uses:
	$ kubectl cert-manager x uninstall
or
	$ kubectl cert-manager x uninstall --remove-crds
or
	$ kubectl cert-manager x uninstall -n new-cert-manager
`

type UninstallOptions struct {
	settings  *cli.EnvSettings
	cfg       *action.Configuration
	client    *action.Install
	valueOpts *values.Options

	ChartName  string
	RemoveCrds bool

	genericclioptions.IOStreams
}

func NewCmdUninstall(ctx context.Context, ioStreams genericclioptions.IOStreams, factory cmdutil.Factory) *cobra.Command {
	settings := cli.New()
	cfg := new(action.Configuration)

	options := &UninstallOptions{
		settings:  settings,
		cfg:       cfg,
		client:    action.NewInstall(cfg),
		valueOpts: &values.Options{},

		IOStreams: ioStreams,
	}

	// Set default namespace cli flag value
	defaults := make(map[string]string)
	defaults["namespace"] = "cert-manager"

	cmd := &cobra.Command{
		Use:   "uninstall",
		Short: "Uninstall cert-manager",
		Long:  uninstallDesc,
		RunE: func(cmd *cobra.Command, args []string) error {
			if err := helm.CopyCliFlags(cmd.Root().PersistentFlags(), defaults, settings); err != nil {
				return nil
			}
			options.client.Namespace = settings.Namespace()

			return options.runUninstall()
		},
		SilenceUsage:  true,
		SilenceErrors: true,
	}

	addInstallUninstallFlags(cmd.Flags(), &options.client.Timeout, &options.client.Wait)

	cmd.Flags().BoolVar(&options.RemoveCrds, "remove-crds", false, "Also remove crds")
	cmd.Flags().StringVar(&options.ChartName, "chart-name", "Cert-manager", "name of the chart to uninstall")

	return cmd
}

func (o *UninstallOptions) runUninstall() error {
	log.SetFlags(0)
	log.SetOutput(o.Out)

	if err := o.cfg.Init(o.settings.RESTClientGetter(), o.settings.Namespace(), os.Getenv("HELM_DRIVER"), log.Printf); err != nil {
		return err
	}

	// Find helm releases linked to a chart with the cert-manager ChartName
	certManagerReleases, err := o.cfg.Releases.List(func(rel *release.Release) bool {
		return rel.Chart.Name() == o.ChartName
	})
	if err != nil {
		return err
	}

	if len(certManagerReleases) > 1 {
		log.Printf(">> Found more than 1 cert-manager installation. Only one one of these installations will get uninstalled. Please rerun to also uninstall the other installations.")
	}

	if len(certManagerReleases) == 0 {
		return fmt.Errorf("No helm-based (installed via helm or the cert-manager kubectl plugin) installation was found.")
	}

	log.Printf(">> Found a helm-based installation, will use the original chart for removal.")
	releaseName, ch, chartValues, err := o.chartAndOptionsFromRelease(certManagerReleases[0])
	if err != nil {
		return err
	}
	o.client.ReleaseName = releaseName

	// Dryrun template generation (used for rendering the resources that should be deleted)
	o.client.DryRun = true                          // Do not apply install
	o.client.IsUpgrade = true                       // Do not validate against cluster
	chartValues[installCRDsFlagName] = o.RemoveCrds // Only render crds if cli flag is provided
	dryRunResult, err := o.client.Run(ch, chartValues)
	if err != nil {
		return err
	}

	// Extract all resources that are present in the chart
	resources, err := helm.GetChartResourceInfo(dryRunResult.Manifest, o.cfg.KubeClient)
	if err != nil {
		return err
	}

	installedResources, err := helm.FetchResources(resources, o.cfg.KubeClient)
	if err != nil {
		return err
	}

	namespacedResources := helm.FilterNamespacedResources(installedResources, o.settings.Namespace())
	crdResources := helm.FilterCrdResources(installedResources)

	// Only delete in case we have found resources in the current namespace OR the only resources found are crds.
	if len(namespacedResources) > 0 || (len(installedResources) > 0 && len(installedResources) == len(crdResources)) {
		// Remove the resources that were generated, by calling the KubeClient directly
		if _, err := o.cfg.KubeClient.Delete(resources); err != nil {
			return fmt.Errorf("failed to delete %s", err)
		}

		if o.client.Wait {
			if err := o.waitForDeletedResources(resources); err != nil {
				return err
			}
		}
	} else if len(installedResources) > 0 {
		// Resources linked to cert-manager were found, but none were found in the current namespace.
		return fmt.Errorf("Only found non-namespaced resources linked to cert-manager. Make sure \"--namespace\" flag is set correctly.")
	} else if !o.RemoveCrds {
		// No resources were found that were generated by a cert-manager installation.
		// But we did not check for crds, so maybe we want to rerun with the --remove-crds flag set?
		log.Printf("Found nothing to uninstall. If you want to remove crds, rerun with the \"--remove-crds\" flag set.")
	} else {
		// No resources were found that were generated by a cert-manager installation.
		log.Printf("Found nothing to uninstall.")
	}

	if len(certManagerReleases) > 0 {
		log.Printf(">> Everything was removed, also removing Helm entry.")
		return o.removeHelmReleaseAndHistory(certManagerReleases[0])
	}

	return nil
}

func (o *UninstallOptions) chartAndOptionsFromRelease(rel *release.Release) (string, *chart.Chart, map[string]interface{}, error) {
	if rel.Config == nil {
		rel.Config = make(map[string]interface{})
	}
	// Overwrite the installCRDs flag so that crds are ONLY removed if the command flag is set
	rel.Config[installCRDsFlagName] = o.RemoveCrds

	return rel.Name, rel.Chart, rel.Config, nil
}

// For sake of simplicity, don't allow to keep history. Equivalent with not allowing
// --keep-hisory flag to be true (https://helm.sh/docs/helm/helm_uninstall/).
func (o *UninstallOptions) removeHelmReleaseAndHistory(rel *release.Release) error {
	rels, err := o.cfg.Releases.History(rel.Name)
	if err != nil {
		return err
	}

	for _, rel := range rels {
		if _, err := o.cfg.Releases.Delete(rel.Name, rel.Version); err != nil {
			return err
		}
	}
	return nil
}

// TODO: wait for uninstall should get merged into Helm (https://github.com/helm/helm/pull/9702)
// waitForDeletedResources polls to check if all the resources are deleted or a timeout is reached
func (o *UninstallOptions) waitForDeletedResources(deleted []*resource.Info) error {
	log.Printf("beginning wait for %d resources to be deleted with timeout of %v", len(deleted), o.client.Timeout)

	ctx, cancel := context.WithTimeout(context.Background(), o.client.Timeout)
	defer cancel()

	return wait.PollImmediateUntil(2*time.Second, func() (bool, error) {
		for _, v := range deleted {
			err := v.Get()
			if err == nil || !apierrors.IsNotFound(err) {
				return false, err
			}
		}
		return true, nil
	}, ctx.Done())
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"testing"
)

func BenchmarkMapLookupKeyStringFromBytes(b *testing.B) {
	entries := 4096
	lookup := make(map[string]int, entries)
	for i := 0; i < entries; i++ {
		lookup[fmt.Sprintf("foo.%d", i)] = -1
	}

	find := []byte("foo.0")

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		// lookup[string(find)] = i
		if _, ok := lookup[string(find)]; !ok {
			b.Fatalf("key %s should exist", string(find))
		}
	}
}

func BenchmarkMapSetKeyStringFromBytes(b *testing.B) {
	entries := 4096
	lookup := make(map[string]int, entries)
	for i := 0; i < entries; i++ {
		lookup[fmt.Sprintf("foo.%d", i)] = -1
	}

	find := []byte("foo.0")

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		lookup[string(find)] = i
	}
}
<commit_msg>Add results for BenchmarkMapLookupKeyStringFromBytes and BenchmarkMapSetKeyStringFromBytes<commit_after>package main

/*
Results
--
$ go test -v -bench BenchmarkMap -benchmem
testing: warning: no tests to run
BenchmarkMapLookupKeyStringFromBytes-4          100000000               19.6 ns/op             0 B/op          0 allocs/op
BenchmarkMapSetKeyStringFromBytes-4             20000000                73.8 ns/op             5 B/op          1 allocs/op
PASS
ok      github.com/robskillington/benchmarks-go 3.561s
*/

import (
	"fmt"
	"testing"
)

func BenchmarkMapLookupKeyStringFromBytes(b *testing.B) {
	entries := 4096
	lookup := make(map[string]int, entries)
	for i := 0; i < entries; i++ {
		lookup[fmt.Sprintf("foo.%d", i)] = -1
	}

	find := []byte("foo.0")

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		if _, ok := lookup[string(find)]; !ok {
			b.Fatalf("key %s should exist", string(find))
		}
	}
}

func BenchmarkMapSetKeyStringFromBytes(b *testing.B) {
	entries := 4096
	lookup := make(map[string]int, entries)
	for i := 0; i < entries; i++ {
		lookup[fmt.Sprintf("foo.%d", i)] = -1
	}

	find := []byte("foo.0")

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		lookup[string(find)] = i
	}
}
<|endoftext|>


<commit_before>package forum

import (
    "errors"
    "github.com/carbocation/util.git/datatypes/binarytree"
    //"fmt"
    "sort"
)

// A ClosureTable should represent every direct-line relationship, including self-to-self
type ClosureTable []Relationship

// A Relationship is the fundamental unit of the closure table. A relationship is
// defined between every entry and itselft, its parent, and any of its parent's ancestors.
type Relationship struct {
    Ancestor int64
    Descendant int64
    Depth int
}

// A Child is intended to be an ephemeral entity that gets validated and converted to a Relationship
type Child struct {
    Parent int64
    Child int64
}

type EmptyTableError int
func (e EmptyTableError) Error() error {
    return errors.New("forum: The closure table is empty, so a parent cannot exist, so a child cannot be added.")
}

func ParentDoesNotExistError() error {
    return errors.New("forum: The closure table contains no record of the requested parent, so no child can be created.")
}

func EntityExistsError() error {
    return errors.New("forum: The entity that you are trying to add to the closure table already exists within it. This operation is not permitted.")
}

// AddChild takes a Child, verifies that it is acceptable, verifies that the
// ClosureTable is suitable to accept a child, and then creates the appropriate
// Relationships within the ClosureTable to instantiate that child.
func (table *ClosureTable) AddChild(new Child) error {
    if len(*table) < 1 {
        return EmptyTableError.Error(1)
    }

    if table.EntityExists(new.Parent) != true {
        return ParentDoesNotExistError()
    }

    if table.EntityExists(new.Child) {
        return EntityExistsError()
    }

    // It checks out, create all of the consequent ancestral relationships:
    // Self
    *table = append(*table, Relationship{Ancestor: new.Child, Descendant: new.Child, Depth: 0})

    // All derived relationships, including the direct parent<->child relationship
    for _, rel := range table.GetAncestralRelationships(new.Parent) {
        *table = append(*table, Relationship{Ancestor: rel.Ancestor, Descendant: new.Child, Depth: rel.Depth+1})
    }

    return nil
}

func (table *ClosureTable) GetAncestralRelationships(id int64) []Relationship {
    list := []Relationship{}
    for _, rel := range *table {
        if rel.Descendant == id {
            list = append(list, rel)
        }
    }

    return list
}

// EntityExists asks if an entity of a given id exists in the closure table
// Entities that exist are guaranteed to appear at least once in ancestor and
// descendant thanks to the self relationship, so the choice of which one to inspect
// is arbitrary
func (table *ClosureTable) EntityExists(id int64) bool {
    for _, r := range *table {
        if r.Descendant == id {
            return true
        }
    }

    return false
}

// Return the id of the root node of the closure table.
// This method assumes that there can only be one root node.
func (table *ClosureTable) RootNodeId() (int64, error) {
    m := map[int64]int{}
    for _, rel := range *table {
        //In go, it's valid to increment an integer in a map without first zeroing it
        m[rel.Descendant]++
    }

    trip := 0
    var result int64
    for item, count := range m {
        if count == 1 {
            result = item
            trip++
        }

        if trip > 1 {
            return int64(-1), errors.New("More than one potential root node was present in the closure table.")
        }
    }

    if trip < 1 {
        return int64(-1), errors.New("No potential root nodes were present in the closure table.")
    }

    return result, nil
}

// Takes a map of entries whose keys are the same values as the IDs of the closure table entries
// Returns a well-formed *binarytree.Tree with those entries as values.
func (table *ClosureTable) TableToTree(entries map[int64]Entry) *binarytree.Tree {
    // Create the tree from the root node:
    //rootNodeId, err := table.RootNodeId()
    /*
    if err != nil {
        return &binarytree.Tree{}
    }
    */

    forest := map[int64]*binarytree.Tree{}

    // All entries now are trees
    for _, entry := range entries {
        forest[entry.Id] = binarytree.New(entry)
    }

    childparent := table.DepthOneRelationships()

    for _, rel := range childparent {
        //fmt.Println(rel,"is a direct child-parent pair")

        // Add the children.
        // If there is already a child, then traverse right until you find nil
        parentTree := forest[rel.Ancestor]
        siblingMode := false

        for {
            //fmt.Println("Trying to set",rel.Descendant,"to be child of",rel.Ancestor)
            if siblingMode {
                //fmt.Println("Went into sibling mode")
                //fmt.Println("parentTree == ",parentTree)
                if parentTree.Right() == nil {
                    // We found an empty slot
                    //fmt.Println("Setting",rel.Descendant,"to be sibling of",rel.Ancestor)
                    parentTree.SetRight(forest[rel.Descendant])
                    forest[rel.Descendant].SetParent(parentTree)
                    //fmt.Println(parentTree)
                    break
                } else {
                    //fmt.Println("Could not set",rel.Descendant,"to be a sibling of",rel.Ancestor,"because it's already occupied.")
                    parentTree = parentTree.Right()
                }
            } else {
                if parentTree.Left() == nil {
                    // We found an empty slot
                    //fmt.Println("Setting",rel.Descendant,"to be child of",rel.Ancestor)
                    parentTree.SetLeft(forest[rel.Descendant])
                    forest[rel.Descendant].SetParent(parentTree)
                    //fmt.Println(parentTree)
                    break
                } else {
                    //fmt.Println("Could not set",rel.Descendant,"to be a child of",rel.Ancestor,"because it's already occupied. We think parentTree is ",parentTree)
                    parentTree = parentTree.Left()
                    siblingMode = true
                }
            }
        }
    }

    //fmt.Println(forest)
    //for _, tree := range forest {
    /*
    x := binarytree.Walker(forest[int64(0)])
    for i := range x {
        fmt.Println("Walked to element",i)
    }
    */
    //}
    /*
    for _, tree := range forest {

    }*/
    /*
    x := binarytree.Walker(forest[int64(0)])
    for i := range x {
        fmt.Println("Walked to element",i)
    }

    for _, entry := range entries {
        forest[entry.Id] = binarytree.New(entry)
    }
    */
    rootNodeId, err := table.RootNodeId()
    if err != nil {
        return &binarytree.Tree{}
    }

    return forest[rootNodeId]

    /*
    // All remaining entries have some sort of ancestral relationship with the root
    // Get the direct parent-child relationships
    childparent := table.DepthOneRelationships()
    fmt.Println(childparent)

    // Additionally, fetch the depth that each entry is from the root node
    depthsFromRoot, deepest := table.DeepestRelationships()
    fmt.Println(depthsFromRoot, deepest)

    m := map[int64](*binarytree.Tree){}

    tree := binarytree.New(entries[rootNodeId])
    m[rootNodeId] = tree

    // Starting from the shallowest max depths, which necessarily are closest to the root:
    for _, depth := range depthsFromRoot {
        for _, rel := deepest[depth] {

        }
        fmt.Println(deepest[depth],"Have a maximum depth of",depth)
    }

    // There must be something much more efficient than this approach
    for len(childparent) > 0 {
        for i, child := range childparent {
            fmt.Println(child.Descendant, "is the immediate child of",child.Ancestor)
            delete(childparent, i)
        }
    }
    */

    //Now add all children.

    //return tree
}

// Returns a map of the ID of each node along with its maximum depth
func (table *ClosureTable) DeepestRelationships() ([]int, map[int][]Relationship) {
    tmp := map[int64]Relationship{}
    out := map[int][]Relationship{}
    discreteDepths := []int{}

    for _, rel := range *table {
        //fmt.Println("For",rel.Descendant,", former best depth was",tmp[rel.Descendant],", new best depth is ",rel.Depth)
        if rel.Depth > tmp[rel.Descendant].Depth {
            tmp[rel.Descendant] = rel
        }
    }

    for _, rel := range tmp {
        //fmt.Println("Appending maxdepth entry",rel,"to depthgroup",rel.Depth)
        out[rel.Depth] = append(out[rel.Depth], rel)
    }

    for depth, _ := range out {
        discreteDepths = append(discreteDepths, depth)
    }

    sort.Ints(discreteDepths)

    return discreteDepths, out
}

// Returns a map of the ID of each node along with its immediate parent
func (table *ClosureTable) DepthOneRelationships() map[int64]Relationship {
    out := map[int64]Relationship{}

    for i, rel := range *table {
        if rel.Depth == 1 {
            out[int64(i)] = rel
        }
    }

    return out
}<commit_msg>Removing commented out code.<commit_after>package forum

import (
    "errors"
    "github.com/carbocation/util.git/datatypes/binarytree"
    "sort"
)

// A ClosureTable should represent every direct-line relationship, including self-to-self
type ClosureTable []Relationship

// A Relationship is the fundamental unit of the closure table. A relationship is
// defined between every entry and itselft, its parent, and any of its parent's ancestors.
type Relationship struct {
    Ancestor int64
    Descendant int64
    Depth int
}

// A Child is intended to be an ephemeral entity that gets validated and converted to a Relationship
type Child struct {
    Parent int64
    Child int64
}

type EmptyTableError int
func (e EmptyTableError) Error() error {
    return errors.New("forum: The closure table is empty, so a parent cannot exist, so a child cannot be added.")
}

func ParentDoesNotExistError() error {
    return errors.New("forum: The closure table contains no record of the requested parent, so no child can be created.")
}

func EntityExistsError() error {
    return errors.New("forum: The entity that you are trying to add to the closure table already exists within it. This operation is not permitted.")
}

// AddChild takes a Child, verifies that it is acceptable, verifies that the
// ClosureTable is suitable to accept a child, and then creates the appropriate
// Relationships within the ClosureTable to instantiate that child.
func (table *ClosureTable) AddChild(new Child) error {
    if len(*table) < 1 {
        return EmptyTableError.Error(1)
    }

    if table.EntityExists(new.Parent) != true {
        return ParentDoesNotExistError()
    }

    if table.EntityExists(new.Child) {
        return EntityExistsError()
    }

    // It checks out, create all of the consequent ancestral relationships:
    // Self
    *table = append(*table, Relationship{Ancestor: new.Child, Descendant: new.Child, Depth: 0})

    // All derived relationships, including the direct parent<->child relationship
    for _, rel := range table.GetAncestralRelationships(new.Parent) {
        *table = append(*table, Relationship{Ancestor: rel.Ancestor, Descendant: new.Child, Depth: rel.Depth+1})
    }

    return nil
}

func (table *ClosureTable) GetAncestralRelationships(id int64) []Relationship {
    list := []Relationship{}
    for _, rel := range *table {
        if rel.Descendant == id {
            list = append(list, rel)
        }
    }

    return list
}

// EntityExists asks if an entity of a given id exists in the closure table
// Entities that exist are guaranteed to appear at least once in ancestor and
// descendant thanks to the self relationship, so the choice of which one to inspect
// is arbitrary
func (table *ClosureTable) EntityExists(id int64) bool {
    for _, r := range *table {
        if r.Descendant == id {
            return true
        }
    }

    return false
}

// Return the id of the root node of the closure table.
// This method assumes that there can only be one root node.
func (table *ClosureTable) RootNodeId() (int64, error) {
    m := map[int64]int{}
    for _, rel := range *table {
        //In go, it's valid to increment an integer in a map without first zeroing it
        m[rel.Descendant]++
    }

    trip := 0
    var result int64
    for item, count := range m {
        if count == 1 {
            result = item
            trip++
        }

        if trip > 1 {
            return int64(-1), errors.New("More than one potential root node was present in the closure table.")
        }
    }

    if trip < 1 {
        return int64(-1), errors.New("No potential root nodes were present in the closure table.")
    }

    return result, nil
}

// Takes a map of entries whose keys are the same values as the IDs of the closure table entries
// Returns a well-formed *binarytree.Tree with those entries as values.
func (table *ClosureTable) TableToTree(entries map[int64]Entry) *binarytree.Tree {
    // Create the tree from the root node:
    forest := map[int64]*binarytree.Tree{}

    // All entries now are trees
    for _, entry := range entries {
        forest[entry.Id] = binarytree.New(entry)
    }

    childparent := table.DepthOneRelationships()

    for _, rel := range childparent {
        // Add the children.
        // If there is already a child, then traverse right until you find nil
        parentTree := forest[rel.Ancestor]
        siblingMode := false

        for {
            if siblingMode {
                if parentTree.Right() == nil {
                    // We found an empty slot
                    parentTree.SetRight(forest[rel.Descendant])
                    forest[rel.Descendant].SetParent(parentTree)
                    break
                } else {
                    parentTree = parentTree.Right()
                }
            } else {
                if parentTree.Left() == nil {
                    // We found an empty slot
                    parentTree.SetLeft(forest[rel.Descendant])
                    forest[rel.Descendant].SetParent(parentTree)
                    break
                } else {
                    parentTree = parentTree.Left()
                    siblingMode = true
                }
            }
        }
    }

    rootNodeId, err := table.RootNodeId()
    if err != nil {
        return &binarytree.Tree{}
    }

    return forest[rootNodeId]
}

// Returns a map of the ID of each node along with its maximum depth
func (table *ClosureTable) DeepestRelationships() ([]int, map[int][]Relationship) {
    tmp := map[int64]Relationship{}
    out := map[int][]Relationship{}
    discreteDepths := []int{}

    for _, rel := range *table {
        if rel.Depth > tmp[rel.Descendant].Depth {
            tmp[rel.Descendant] = rel
        }
    }

    for _, rel := range tmp {
        out[rel.Depth] = append(out[rel.Depth], rel)
    }

    for depth, _ := range out {
        discreteDepths = append(discreteDepths, depth)
    }

    sort.Ints(discreteDepths)

    return discreteDepths, out
}

// Returns a map of the ID of each node along with its immediate parent
func (table *ClosureTable) DepthOneRelationships() map[int64]Relationship {
    out := map[int64]Relationship{}

    for i, rel := range *table {
        if rel.Depth == 1 {
            out[int64(i)] = rel
        }
    }

    return out
}
<|endoftext|>


<commit_before>package cookies

import (
	"net/http"
	"net/url"
	"time"

	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/setting"
)

type CookieOptions struct {
	Path             string
	Secure           bool
	SameSiteDisabled bool
	SameSiteMode     http.SameSite
}

func newCookieOptions() CookieOptions {
	path := "/"
	if len(setting.AppSubUrl) > 0 {
		path = setting.AppSubUrl
	}
	return CookieOptions{
		Path:             path,
		Secure:           setting.CookieSecure,
		SameSiteDisabled: setting.CookieSameSiteDisabled,
		SameSiteMode:     setting.CookieSameSiteMode,
	}
}

type getCookieOptionsFunc func() CookieOptions

func DeleteCookie(w http.ResponseWriter, name string, getCookieOptions getCookieOptionsFunc) {
	WriteCookie(w, name, "", -1, getCookieOptions)
}

func WriteCookie(w http.ResponseWriter, name string, value string, maxAge int, getCookieOptions getCookieOptionsFunc) {
	if getCookieOptions == nil {
		getCookieOptions = newCookieOptions
	}

	options := getCookieOptions()
	cookie := http.Cookie{
		Name:     name,
		MaxAge:   maxAge,
		Value:    value,
		HttpOnly: true,
		Path:     options.Path,
		Secure:   options.Secure,
	}
	if !options.SameSiteDisabled {
		cookie.SameSite = options.SameSiteMode
	}
	http.SetCookie(w, &cookie)
}

func WriteSessionCookie(ctx *models.ReqContext, cfg *setting.Cfg, value string, maxLifetime time.Duration) {
	if cfg.Env == setting.Dev {
		ctx.Logger.Info("New token", "unhashed token", value)
	}

	var maxAge int
	if maxLifetime <= 0 {
		maxAge = -1
	} else {
		maxAge = int(maxLifetime.Seconds())
	}

	WriteCookie(ctx.Resp, cfg.LoginCookieName, url.QueryEscape(value), maxAge, nil)
}
<commit_msg>Chore: Make NewCookieOptions exported in cookies.go (#56476)<commit_after>package cookies

import (
	"net/http"
	"net/url"
	"time"

	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/setting"
)

type CookieOptions struct {
	Path             string
	Secure           bool
	SameSiteDisabled bool
	SameSiteMode     http.SameSite
}

func NewCookieOptions() CookieOptions {
	path := "/"
	if len(setting.AppSubUrl) > 0 {
		path = setting.AppSubUrl
	}
	return CookieOptions{
		Path:             path,
		Secure:           setting.CookieSecure,
		SameSiteDisabled: setting.CookieSameSiteDisabled,
		SameSiteMode:     setting.CookieSameSiteMode,
	}
}

type getCookieOptionsFunc func() CookieOptions

func DeleteCookie(w http.ResponseWriter, name string, getCookieOptions getCookieOptionsFunc) {
	WriteCookie(w, name, "", -1, getCookieOptions)
}

func WriteCookie(w http.ResponseWriter, name string, value string, maxAge int, getCookieOptions getCookieOptionsFunc) {
	if getCookieOptions == nil {
		getCookieOptions = NewCookieOptions
	}

	options := getCookieOptions()
	cookie := http.Cookie{
		Name:     name,
		MaxAge:   maxAge,
		Value:    value,
		HttpOnly: true,
		Path:     options.Path,
		Secure:   options.Secure,
	}
	if !options.SameSiteDisabled {
		cookie.SameSite = options.SameSiteMode
	}
	http.SetCookie(w, &cookie)
}

func WriteSessionCookie(ctx *models.ReqContext, cfg *setting.Cfg, value string, maxLifetime time.Duration) {
	if cfg.Env == setting.Dev {
		ctx.Logger.Info("New token", "unhashed token", value)
	}

	var maxAge int
	if maxLifetime <= 0 {
		maxAge = -1
	} else {
		maxAge = int(maxLifetime.Seconds())
	}

	WriteCookie(ctx.Resp, cfg.LoginCookieName, url.QueryEscape(value), maxAge, nil)
}
<|endoftext|>