<commit_before>package comparer

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"
)

const (
	pathSep = string(os.PathSeparator)
)

var (
	// DocType is required If you want to use the package, so don't
	// forget to instantiate It before using the Compare function
	DocType string
	// Docs , Lines and InNeed are `metrics` of how the program is running
	Docs   int
	Lines  int
	InNeed int
)

// Compare is the function that takes two comparable paths to
// directories and writes It's differences into the translation's
// directories files
func Compare(original, translation string) (err error) {
	originalDir, err := ReadDir(original)
	if err != nil {
		return
	}
	for _, f := range originalDir {
		if f.IsDir() {
			err = checkTransDirExists(f.Name(), translation)
			if err != nil {
				return
			}
			err = Compare(filepath.Join(original, f.Name()), filepath.Join(translation, f.Name()))
			if err != nil {
				return
			}
		} else {
			Docs += 2
			err = readFiles(filepath.Join(original, f.Name()), filepath.Join(translation, f.Name()))
			if err != nil {
				return
			}
		}
	}
	return
}

func ReadDir(path string) ([]os.FileInfo, error) {
	err := os.Chdir(path)
	if err != nil {
		return nil, err
	}
	fi, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	file, err := fi.Readdir(0)
	if err != nil {
		return nil, err
	}
	defer fi.Close()
	return file, nil
}

func readFiles(orgF, trltF string) (err error) {
	err = os.Chdir(filepath.Dir(orgF))
	if err != nil {
		return
	}
	fName := strings.Split(orgF, pathSep)
	fileName := fName[len(fName)-1]
	orgTags, err := readFile(fileName, filepath.Dir(orgF))
	if err != nil {
		return
	}
	fName = strings.Split(trltF, pathSep)
	fileName = fName[len(fName)-1]
	trltTags, err := readFile(fileName, filepath.Dir(trltF))
	if err != nil {
		err = os.Chdir(filepath.Dir(trltF))
		if err != nil {
			return
		}
		file, errCreate := os.Create(fileName)
		if errCreate != nil {
			return errCreate
		}
		defer file.Close()
	}
	missingTags := findMissing(orgTags, trltTags)
	if missingTags == nil {
		return
	}
	outdatedTags := findMissing(trltTags, orgTags)
	err = writeToFileMissingTags(trltF, outdatedTags, true)
	if err != nil {
		return
	}
	err = writeToFileMissingTags(trltF, missingTags, false)
	if err != nil {
		return
	}
	return
}

func writeToFileMissingTags(translationFilePath string, missingTags map[string]string, outdated bool) (err error) {
	f, err := os.OpenFile(translationFilePath, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		return
	}
	defer f.Close()
	for missingKey, missingValue := range missingTags {
		if string(missingKey[1]) == pathSep {
			continue
		}
		InNeed++
		if (hasSubstring(missingKey, "<!-")) || (hasSubstring(missingKey, "<--")) || hasSubstring(missingKey, "<?"+DocType) {
			_, err = f.WriteString(fmt.Sprintf("\n%s", missingKey))
			if err != nil {
				return
			}
			continue
		}
		if outdated {
			_, err = f.WriteString(fmt.Sprintf("\n[OUTDATED]%s", missingKey))
			if err != nil {
				return
			}
			continue
		}
		_, err = f.WriteString(fmt.Sprintf("\n%s%s</%s", missingKey, missingValue, missingKey[1:]))
		if err != nil {
			return
		}
	}
	return
}

func hasSubstring(str, s string) bool {
	return strings.Contains(s, str)
}

func readFile(fileName, filePath string) (map[string]string, error) {
	splittedFileName := strings.Split(fileName, ".")
	if splittedFileName[len(splittedFileName)-1] != DocType {
		return nil, nil
	}
	inFile, err := os.Open(filepath.Join(filePath, fileName))
	if err != nil {
		return nil, err
	}
	defer inFile.Close()
	tags := map[string]string{}
	scanner := bufio.NewScanner(inFile)
	for scanner.Scan() {
		Lines++
		line := scanner.Text()
		indexStart := strings.Index(line, "<")
		indexEnd := strings.Index(line, ">")
		if (len(line) == 0) || indexStart < 0 || indexEnd < 0 {
			continue
		}
		tag := line[indexStart : indexEnd+1]
		markers := strings.Split(tag, " ")
		if string(tag[0]) == pathSep {
			continue
		}
		tag = markers[0]
		valEnd := strings.LastIndex(line, "<")
		if valEnd < indexEnd {
			continue
		}
		translationValue := line[indexEnd+1 : valEnd]
		if (indexStart != -1) && (indexEnd != -1) {
			tags[tag] = translationValue
		}
	}
	return tags, nil
}

func findMissing(original, translation map[string]string) map[string]string {
	missing := make(map[string]string)
	if reflect.DeepEqual(original, translation) {
		return nil
	}
	for k, v := range original {
		if _, ok := translation[k]; !ok {
			missing[k] = v
		}
	}
	return missing
}

// TODO: refactor
// this should return a bool and an error,
// this way the upper func can handle dir creation
func checkTransDirExists(dir, translation string) (err error) {
	splitDir := strings.Split(dir, pathSep)
	dir = filepath.Join(translation, splitDir[len(splitDir)-1])
	_, err = os.Open(dir)
	if err != nil {
		splitedDirectory := strings.Split(dir, pathSep)
		parentDirFromSplit := dir[:len(dir)-len(splitedDirectory[len(splitedDirectory)-1])-1]
		os.Chdir(parentDirFromSplit)
		err = os.Mkdir(splitedDirectory[len(splitedDirectory)-1], 0700)
		if err != nil {
			return
		}
	}
	return
}
<commit_msg>comparer: refactor to reduce func complexity<commit_after>package comparer

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"
)

const (
	pathSep = string(os.PathSeparator)
)

var (
	// DocType is required If you want to use the package, so don't
	// forget to instantiate It before using the Compare function
	DocType string
	// Docs , Lines and InNeed are `metrics` of how the program is running
	Docs   int
	Lines  int
	InNeed int
)

// Compare is the function that takes two comparable paths to
// directories and writes It's differences into the translation's
// directories files
func Compare(original, translation string) (err error) {
	originalDir, err := ReadDir(original)
	if err != nil {
		return
	}
	for _, f := range originalDir {
		if f.IsDir() {
			err = checkTransDirExists(f.Name(), translation)
			if err != nil {
				return
			}
			err = Compare(filepath.Join(original, f.Name()), filepath.Join(translation, f.Name()))
			if err != nil {
				return
			}
		} else {
			Docs += 2
			err = readFiles(filepath.Join(original, f.Name()), filepath.Join(translation, f.Name()))
			if err != nil {
				return
			}
		}
	}
	return
}

func ReadDir(path string) ([]os.FileInfo, error) {
	err := os.Chdir(path)
	if err != nil {
		return nil, err
	}
	fi, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	file, err := fi.Readdir(0)
	if err != nil {
		return nil, err
	}
	defer fi.Close()
	return file, nil
}

func readFiles(orgF, trltF string) (err error) {
	err = os.Chdir(filepath.Dir(orgF))
	if err != nil {
		return
	}
	fName := strings.Split(orgF, pathSep)
	fileName := fName[len(fName)-1]
	orgTags, err := readFile(fileName, filepath.Dir(orgF))
	if err != nil {
		return
	}
	fName = strings.Split(trltF, pathSep)
	fileName = fName[len(fName)-1]
	trltTags, err := readFile(fileName, filepath.Dir(trltF))
	if err != nil {
		err = os.Chdir(filepath.Dir(trltF))
		if err != nil {
			return
		}
		file, errCreate := os.Create(fileName)
		if errCreate != nil {
			return errCreate
		}
		defer file.Close()
	}
	missingTags := findMissing(orgTags, trltTags)
	if missingTags == nil {
		return
	}
	outdatedTags := findMissing(trltTags, orgTags)
	err = writeToFileMissingTags(trltF, outdatedTags, true)
	if err != nil {
		return
	}
	err = writeToFileMissingTags(trltF, missingTags, false)
	if err != nil {
		return
	}
	return
}

func writeToFileMissingTags(translationFilePath string, missingTags map[string]string, outdated bool) (err error) {
	f, err := os.OpenFile(translationFilePath, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		return
	}
	defer f.Close()
	for missingKey, missingValue := range missingTags {
		if string(missingKey[1]) == pathSep {
			continue
		}
		InNeed++
		if isCommentaryOrDocType(missingKey) {
			_, err = f.WriteString(fmt.Sprintf("\n%s", missingKey))
			if err != nil {
				return
			}
			continue
		}
		if outdated {
			_, err = f.WriteString(fmt.Sprintf("\n[OUTDATED]%s", missingKey))
			if err != nil {
				return
			}
			continue
		}
		_, err = f.WriteString(fmt.Sprintf("\n%s%s</%s", missingKey, missingValue, missingKey[1:]))
		if err != nil {
			return
		}
	}
	return
}

func isCommentaryOrDocType(key string) bool {
	if (hasSubstring(key, "<!-")) || (hasSubstring(key, "<--")) || hasSubstring(key, "<?"+DocType) {
		return true
	}
	return false
}

func hasSubstring(str, s string) bool {
	return strings.Contains(s, str)
}

func readFile(fileName, filePath string) (map[string]string, error) {
	splittedFileName := strings.Split(fileName, ".")
	if splittedFileName[len(splittedFileName)-1] != DocType {
		return nil, nil
	}
	inFile, err := os.Open(filepath.Join(filePath, fileName))
	if err != nil {
		return nil, err
	}
	defer inFile.Close()
	tags := map[string]string{}
	scanner := bufio.NewScanner(inFile)
	for scanner.Scan() {
		Lines++
		line := scanner.Text()
		indexStart := strings.Index(line, "<")
		indexEnd := strings.Index(line, ">")
		if (len(line) == 0) || indexStart < 0 || indexEnd < 0 {
			continue
		}
		tag := line[indexStart : indexEnd+1]
		markers := strings.Split(tag, " ")
		if string(tag[0]) == pathSep {
			continue
		}
		tag = markers[0]
		valEnd := strings.LastIndex(line, "<")
		if valEnd < indexEnd {
			continue
		}
		translationValue := line[indexEnd+1 : valEnd]
		if (indexStart != -1) && (indexEnd != -1) {
			tags[tag] = translationValue
		}
	}
	return tags, nil
}

func findMissing(original, translation map[string]string) map[string]string {
	missing := make(map[string]string)
	if reflect.DeepEqual(original, translation) {
		return nil
	}
	for k, v := range original {
		if _, ok := translation[k]; !ok {
			missing[k] = v
		}
	}
	return missing
}

// TODO: refactor
// this should return a bool and an error,
// this way the upper func can handle dir creation
func checkTransDirExists(dir, translation string) (err error) {
	splitDir := strings.Split(dir, pathSep)
	dir = filepath.Join(translation, splitDir[len(splitDir)-1])
	_, err = os.Open(dir)
	if err != nil {
		splitedDirectory := strings.Split(dir, pathSep)
		parentDirFromSplit := dir[:len(dir)-len(splitedDirectory[len(splitedDirectory)-1])-1]
		os.Chdir(parentDirFromSplit)
		err = os.Mkdir(splitedDirectory[len(splitedDirectory)-1], 0700)
		if err != nil {
			return
		}
	}
	return
}
<|endoftext|>


<commit_before>package comparer

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"
)

const (
	pathSep = string(os.PathSeparator)
)

var (
	// DocType is required If you want to use the package, so don't
	// forget to instantiate It before using the Compare function
	DocType string
	// Docs , Lines and InNeed are `metrics` of how the program is running
	Docs   int
	Lines  int
	InNeed int
)

// Compare is the function that takes two comparable paths to
// directories and writes It's differences into the translation's
// directories files
func Compare(original, translation string) (err error) {
	originalDir, err := ReadDir(original)
	if err != nil {
		return
	}
	for _, f := range originalDir {
		if f.IsDir() {
			err = checkTransDirExists(f.Name(), translation)
			if err != nil {
				return
			}
			err = Compare(filepath.Join(original, f.Name()), filepath.Join(translation, f.Name()))
			if err != nil {
				return
			}
		} else {
			Docs += 2
			err = readFiles(filepath.Join(original, f.Name()), filepath.Join(translation, f.Name()))
			if err != nil {
				return
			}
		}
	}
	return
}

func ReadDir(path string) ([]os.FileInfo, error) {
	err := os.Chdir(path)
	if err != nil {
		return nil, err
	}
	fi, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	file, err := fi.Readdir(0)
	if err != nil {
		return nil, err
	}
	defer fi.Close()
	return file, nil
}

func readFiles(orgF, trltF string) (err error) {
	err = os.Chdir(filepath.Dir(orgF))
	if err != nil {
		return
	}
	fName := strings.Split(orgF, pathSep)
	fileName := fName[len(fName)-1]
	orgTags, err := readFile(fileName, filepath.Dir(orgF))
	if err != nil {
		return
	}
	fName = strings.Split(trltF, pathSep)
	fileName = fName[len(fName)-1]
	trltTags, err := readFile(fileName, filepath.Dir(trltF))
	if err != nil {
		err = os.Chdir(filepath.Dir(trltF))
		if err != nil {
			return
		}
		file, errCreate := os.Create(fileName)
		if errCreate != nil {
			return errCreate
		}
		defer file.Close()
	}
	missingTags := findMissing(orgTags, trltTags)
	if missingTags == nil {
		return
	}
	outdatedTags := findMissing(trltTags, orgTags)
	err = writeToFileMissingTags(trltF, outdatedTags, true)
	if err != nil {
		return
	}
	err = writeToFileMissingTags(trltF, missingTags, false)
	if err != nil {
		return
	}
	return
}

func writeToFileMissingTags(translationFilePath string, missingTags map[string]string, outdated bool) (err error) {
	f, err := os.OpenFile(translationFilePath, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		return
	}
	defer f.Close()
	for missingKey, missingValue := range missingTags {
		if len(missingKey) == 0 {
			continue
		}
		if string(missingKey[1]) == pathSep {
			continue
		}
		InNeed++
		if isCommentaryOrDocType(missingKey) {
			_, err = f.WriteString(fmt.Sprintf("\n%s", missingKey))
			if err != nil {
				return
			}
			continue
		}
		if outdated {
			_, err = f.WriteString(fmt.Sprintf("\n[OUTDATED]%s", missingKey))
			if err != nil {
				return
			}
			continue
		}
		_, err = f.WriteString(fmt.Sprintf("\n%s%s</%s", missingKey, missingValue, missingKey[1:]))
		if err != nil {
			return
		}
	}
	return
}

func isCommentaryOrDocType(key string) bool {
	if (hasSubstring(key, "<!-")) || (hasSubstring(key, "<--")) || hasSubstring(key, "<?"+DocType) {
		return true
	}
	return false
}

func hasSubstring(str, s string) bool {
	return strings.Contains(s, str)
}

func readFile(fileName, filePath string) (map[string]string, error) {
	splittedFileName := strings.Split(fileName, ".")
	if splittedFileName[len(splittedFileName)-1] != DocType {
		return nil, nil
	}
	inFile, err := os.Open(filepath.Join(filePath, fileName))
	if err != nil {
		return nil, err
	}
	defer inFile.Close()
	tags := map[string]string{}
	scanner := bufio.NewScanner(inFile)
	for scanner.Scan() {
		Lines++
		line := scanner.Text()
		indexStart := strings.Index(line, "<")
		indexEnd := strings.Index(line, ">")
		if (len(line) == 0) || indexStart < 0 || indexEnd < 0 {
			continue
		}
		tag := line[indexStart : indexEnd+1]
		markers := strings.Split(tag, " ")
		if string(tag[0]) == pathSep {
			continue
		}
		tag = markers[0]
		valEnd := strings.LastIndex(line, "<")
		if valEnd < indexEnd {
			continue
		}
		translationValue := line[indexEnd+1 : valEnd]
		if (indexStart != -1) && (indexEnd != -1) {
			tags[tag] = translationValue
		}
	}
	return tags, nil
}

func findMissing(original, translation map[string]string) map[string]string {
	missing := make(map[string]string)
	if reflect.DeepEqual(original, translation) {
		return nil
	}
	for k, v := range original {
		if _, ok := translation[k]; !ok {
			missing[k] = v
		}
	}
	return missing
}

// TODO: refactor
// this should return a bool and an error,
// this way the upper func can handle dir creation
func checkTransDirExists(dir, translation string) (err error) {
	splitDir := strings.Split(dir, pathSep)
	dir = filepath.Join(translation, splitDir[len(splitDir)-1])
	_, err = os.Open(dir)
	if err != nil {
		splitedDirectory := strings.Split(dir, pathSep)
		parentDirFromSplit := dir[:len(dir)-len(splitedDirectory[len(splitedDirectory)-1])-1]
		os.Chdir(parentDirFromSplit)
		err = os.Mkdir(splitedDirectory[len(splitedDirectory)-1], 0700)
		if err != nil {
			return
		}
	}
	return
}
<commit_msg>comparer: rename parameters in readFiles<commit_after>package comparer

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"reflect"
	"strings"
)

const (
	pathSep = string(os.PathSeparator)
)

var (
	// DocType is required If you want to use the package, so don't
	// forget to instantiate It before using the Compare function
	DocType string
	// Docs , Lines and InNeed are `metrics` of how the program is running
	Docs   int
	Lines  int
	InNeed int
)

// Compare is the function that takes two comparable paths to
// directories and writes It's differences into the translation's
// directories files
func Compare(original, translation string) (err error) {
	originalDir, err := ReadDir(original)
	if err != nil {
		return
	}
	for _, f := range originalDir {
		if f.IsDir() {
			err = checkTransDirExists(f.Name(), translation)
			if err != nil {
				return
			}
			err = Compare(filepath.Join(original, f.Name()), filepath.Join(translation, f.Name()))
			if err != nil {
				return
			}
		} else {
			Docs += 2
			err = readFiles(filepath.Join(original, f.Name()), filepath.Join(translation, f.Name()))
			if err != nil {
				return
			}
		}
	}
	return
}

func ReadDir(path string) ([]os.FileInfo, error) {
	err := os.Chdir(path)
	if err != nil {
		return nil, err
	}
	fi, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	file, err := fi.Readdir(0)
	if err != nil {
		return nil, err
	}
	defer fi.Close()
	return file, nil
}

func readFiles(originalFile, translationFile string) (err error) {
	err = os.Chdir(filepath.Dir(originalFile))
	if err != nil {
		return
	}
	fName := strings.Split(originalFile, pathSep)
	fileName := fName[len(fName)-1]
	orgTags, err := readFile(fileName, filepath.Dir(originalFile))
	if err != nil {
		return
	}
	fName = strings.Split(translationFile, pathSep)
	fileName = fName[len(fName)-1]
	trltTags, err := readFile(fileName, filepath.Dir(translationFile))
	if err != nil {
		err = os.Chdir(filepath.Dir(translationFile))
		if err != nil {
			return
		}
		file, errCreate := os.Create(fileName)
		if errCreate != nil {
			return errCreate
		}
		defer file.Close()
	}
	missingTags := findMissing(orgTags, trltTags)
	if missingTags == nil {
		return
	}
	outdatedTags := findMissing(trltTags, orgTags)
	err = writeToFileMissingTags(translationFile, outdatedTags, true)
	if err != nil {
		return
	}
	err = writeToFileMissingTags(translationFile, missingTags, false)
	if err != nil {
		return
	}
	return
}

func writeToFileMissingTags(translationFilePath string, missingTags map[string]string, outdated bool) (err error) {
	f, err := os.OpenFile(translationFilePath, os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		return
	}
	defer f.Close()
	for missingKey, missingValue := range missingTags {
		if len(missingKey) == 0 {
			continue
		}
		if string(missingKey[1]) == pathSep {
			continue
		}
		InNeed++
		if isCommentaryOrDocType(missingKey) {
			_, err = f.WriteString(fmt.Sprintf("\n%s", missingKey))
			if err != nil {
				return
			}
			continue
		}
		if outdated {
			_, err = f.WriteString(fmt.Sprintf("\n[OUTDATED]%s", missingKey))
			if err != nil {
				return
			}
			continue
		}
		_, err = f.WriteString(fmt.Sprintf("\n%s%s</%s", missingKey, missingValue, missingKey[1:]))
		if err != nil {
			return
		}
	}
	return
}

func isCommentaryOrDocType(key string) bool {
	if (hasSubstring(key, "<!-")) || (hasSubstring(key, "<--")) || hasSubstring(key, "<?"+DocType) {
		return true
	}
	return false
}

func hasSubstring(str, s string) bool {
	return strings.Contains(s, str)
}

func readFile(fileName, filePath string) (map[string]string, error) {
	splittedFileName := strings.Split(fileName, ".")
	if splittedFileName[len(splittedFileName)-1] != DocType {
		return nil, nil
	}
	inFile, err := os.Open(filepath.Join(filePath, fileName))
	if err != nil {
		return nil, err
	}
	defer inFile.Close()
	tags := map[string]string{}
	scanner := bufio.NewScanner(inFile)
	for scanner.Scan() {
		Lines++
		line := scanner.Text()
		indexStart := strings.Index(line, "<")
		indexEnd := strings.Index(line, ">")
		if (len(line) == 0) || indexStart < 0 || indexEnd < 0 {
			continue
		}
		tag := line[indexStart : indexEnd+1]
		markers := strings.Split(tag, " ")
		if string(tag[0]) == pathSep {
			continue
		}
		tag = markers[0]
		valEnd := strings.LastIndex(line, "<")
		if valEnd < indexEnd {
			continue
		}
		translationValue := line[indexEnd+1 : valEnd]
		if (indexStart != -1) && (indexEnd != -1) {
			tags[tag] = translationValue
		}
	}
	return tags, nil
}

func findMissing(original, translation map[string]string) map[string]string {
	missing := make(map[string]string)
	if reflect.DeepEqual(original, translation) {
		return nil
	}
	for k, v := range original {
		if _, ok := translation[k]; !ok {
			missing[k] = v
		}
	}
	return missing
}

// TODO: refactor
// this should return a bool and an error,
// this way the upper func can handle dir creation
func checkTransDirExists(dir, translation string) (err error) {
	splitDir := strings.Split(dir, pathSep)
	dir = filepath.Join(translation, splitDir[len(splitDir)-1])
	_, err = os.Open(dir)
	if err != nil {
		splitedDirectory := strings.Split(dir, pathSep)
		parentDirFromSplit := dir[:len(dir)-len(splitedDirectory[len(splitedDirectory)-1])-1]
		os.Chdir(parentDirFromSplit)
		err = os.Mkdir(splitedDirectory[len(splitedDirectory)-1], 0700)
		if err != nil {
			return
		}
	}
	return
}
<|endoftext|>


<commit_before>// Copyright 2014 The Cockroach 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. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

package util

import (
	"fmt"
	"path/filepath"
	"runtime"
)

const errorPrefixFormat string = "%s:%d: "

// getPrefix skips two stack frames to get the file & line number of
// original caller.
func getPrefix(format string) string {
	if _, file, line, ok := runtime.Caller(2); ok {
		return fmt.Sprintf(errorPrefixFormat, filepath.Base(file), line)
	}
	return ""
}

// Errorf is a passthrough to fmt.Errorf, with an additional prefix
// containing the filename and line number.
func Errorf(format string, a ...interface{}) error {
	return fmt.Errorf(getPrefix(errorPrefixFormat)+format, a...)
}

// Error is a passthrough to fmt.Error, with an additional prefix
// containing the filename and line number.
func Error(a ...interface{}) error {
	prefix := getPrefix(errorPrefixFormat)
	if prefix != "" {
		a = append([]interface{}{prefix}, a...)
	}
	return fmt.Errorf("%s", fmt.Sprint(a...))
}
<commit_msg>Use parameter, not constant<commit_after>// Copyright 2014 The Cockroach 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. See the AUTHORS file
// for names of contributors.
//
// Author: Spencer Kimball (spencer.kimball@gmail.com)

package util

import (
	"fmt"
	"path/filepath"
	"runtime"
)

const errorPrefixFormat string = "%s:%d: "

// getPrefix skips two stack frames to get the file & line number of
// original caller.
func getPrefix(format string) string {
	if _, file, line, ok := runtime.Caller(2); ok {
		return fmt.Sprintf(format, filepath.Base(file), line)
	}
	return ""
}

// Errorf is a passthrough to fmt.Errorf, with an additional prefix
// containing the filename and line number.
func Errorf(format string, a ...interface{}) error {
	return fmt.Errorf(getPrefix(errorPrefixFormat)+format, a...)
}

// Error is a passthrough to fmt.Error, with an additional prefix
// containing the filename and line number.
func Error(a ...interface{}) error {
	prefix := getPrefix(errorPrefixFormat)
	if prefix != "" {
		a = append([]interface{}{prefix}, a...)
	}
	return fmt.Errorf("%s", fmt.Sprint(a...))
}
<|endoftext|>


<commit_before>package utils

import (
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/Jeffail/gabs"
	log "github.com/cihub/seelog"
	"github.com/fsouza/go-dockerclient"
	"io/ioutil"
	"net"
	"os"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	countFile = "/tmp/logspout/logspout.json"
)

var (
	UUID        string
	M1          map[string]string
	IP          string
	Hostname    string
	UserId      string
	ClusterId   string
	counter     map[string]int64
	counterlock sync.Mutex
	mesoslock   sync.Mutex
)

func getPort(ports string) string {
	reg := regexp.MustCompile("\\[|\\]")
	strs := strings.Split(reg.ReplaceAllString(ports, ""), "-")
	startPort, serr := strconv.Atoi(strs[0])
	endPort, eerr := strconv.Atoi(strs[1])
	if serr != nil || eerr != nil {
		return "[]"
	}
	var port []string
	for i := startPort; i <= endPort; i++ {
		port = append(port, strconv.Itoa(i))
	}
	return "[" + strings.Join(port, ",") + "]"

}

type Message struct {
	FrameWorks []struct {
		Executors []struct {
			Container string `json:"container"`
			Tasks     []struct {
				SlaveId   string `json:"slave_id"`
				State     string `json:"state"`
				Name      string `json:"name"`
				Id        string `json:"id"`
				Resources struct {
					Ports string `json:"ports"`
				} `json:"resources"`
			} `json:"tasks"`
		} `json:"executors"`
	} `json:"frameworks"`
	HostName string `json:"hostname"`
}

func init() {
	counterlock, mesoslock = sync.Mutex{}, sync.Mutex{}
	M1 = make(map[string]string)
	loadCounter()
	UUID = os.Getenv("HOST_ID")
	if UUID == "" {
		log.Error("cat't found uuid")
		os.Exit(0)
	}
	UserId = os.Getenv("USER_ID")
	if UserId == "" {
		log.Error("cat't found userid")
		os.Exit(0)
	}
	ClusterId = os.Getenv("CLUSTER_ID")
	if ClusterId == "" {
		log.Error("cat't found clusterid")
		os.Exit(0)
	}
	Hostname, _ = os.Hostname()
	IP, _ = GetIp()
	M1 = getCnames()
}

func Run() {
	timer := time.NewTicker(5 * time.Second)
	for {
		select {
		case <-timer.C:
			//getMesosInfo(mesoslock)
			persistenCounter()
		}
	}
}

func getMesosInfo(lock *sync.Mutex) {
	data, err := HttpGet("http://" + IP + ":5051/slave(1)/state.json")
	if err == nil {
		mg := getCnames()
		var m Message
		json.Unmarshal([]byte(data), &m)
		if len(m.FrameWorks) > 0 {
			for _, fw := range m.FrameWorks {
				if len(fw.Executors) > 0 {
					for _, ex := range fw.Executors {
						if len(ex.Tasks) > 0 {
							for _, ts := range ex.Tasks {
								mcn := "/mesos-" + ts.SlaveId + "." + ex.Container
								mg[mcn] = ts.Name + " " +
									ts.Id + " " +
									getPort(ts.Resources.Ports)
							}
						}
					}
				}
			}
		}
		lock.Lock()
		M1 = mg
		lock.Unlock()
	}
	//log.Debug("get mesos json: ", err, M1)
}

func GetIp() (ip string, err error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ip = ip.To4()
			if ip == nil {
				continue // not an ipv4 address
			}
			return ip.String(), nil
		}
	}
	return "", errors.New("are you connected to the network?")
}

func ConnTCP(address string) (net.Conn, error) {
	raddr, err := net.ResolveTCPAddr("tcp", address)
	if err != nil {
		return nil, err
	}
	conn, err := net.DialTCP("tcp", nil, raddr)
	if err != nil {
		return nil, err
	}
	return conn, nil
}

func ConnTLS(address string) (net.Conn, error) {
	cert, err := tls.LoadX509KeyPair("/root/ssl/client.pem", "/root/ssl/client.key")
	if err != nil {
		return nil, err
	}
	config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
	conn, err := tls.Dial("tcp", address, &config)
	if err != nil {
		return nil, err
	}
	return conn, nil
}

func getCnames() map[string]string {
	cnames := os.Getenv("CNAMES")
	if cnames != "" {
		cmap := make(map[string]string)
		ca := strings.Split(cnames, ",")
		for _, cname := range ca {
			rname := strings.Replace(cname, "/", "", 1)
			cmap[cname] = rname + " " + rname + " " + "[]"
		}
		return cmap
	}
	return make(map[string]string)
}

func SendMessage(cn, msg string, d *docker.Container) string {
	counter[d.ID]++
	t := time.Now()
	timestr := t.Format("2006-01-02T15:04:05.895+08:00")
	logmsg := timestr + " " +
		UserId + " " +
		fmt.Sprint(counter[d.ID]) + " " +
		ClusterId + " " +
		UUID + " " +
		IP + " " +
		Hostname + " " +
		cn + " " +
		msg
	return logmsg
}

func loadCounter() {
	counter = make(map[string]int64)
	buf, err := ioutil.ReadFile(countFile)
	if err == nil {
		json, err := gabs.ParseJSON(buf)
		if err == nil {
			m, err := json.ChildrenMap()
			if err == nil {
				for k, v := range m {
					if reflect.TypeOf(v.Data()).String() == "float64" {
						counter[k] = int64(v.Data().(float64))
					}
				}
				log.Debug("load container counter: ", json)
			} else {
				log.Error("counter childrenmap err: ", err)
			}
		} else {
			log.Error("counter str to json err: ", err)
		}
	} else {
		log.Debug("not found counter file: ", err)
	}
}

func persistenCounter() {
	if len(counter) > 0 {
		json := gabs.New()
		counterlock.Lock()
		for k, v := range counter {
			json.Set(v, k)
		}
		counterlock.Unlock()
		err := ioutil.WriteFile(countFile, json.Bytes(), 0x644)
		if err != nil {
			log.Error("persisten counter failed: ", err)
		}
	}
}

func DeleteCounter(client *docker.Client) {
	containers, err := client.ListContainers(docker.ListContainersOptions{})
	if err == nil {
		copycounter := counter
		for _, container := range containers {
			_, ok := copycounter[container.ID]
			if ok {
				delete(copycounter, container.ID)
			}
		}

		counterlock.Lock()
		for k, _ := range copycounter {
			delete(counter, k)
		}
		counterlock.Unlock()
	}
}

func DeleteM1(cname string) {
	if _, ok := getCnames()[cname]; !ok {
		mesoslock.Lock()
		delete(M1, cname)
		mesoslock.Unlock()
	}
}

func ReceiveContainer(container *docker.Container) {
	env := container.Config.Env
	appName := ""
	taskId := ""
	ports := ""
	if len(env) > 0 {
		for _, e := range env {
			if strings.Contains(e, "MARATHON_APP_ID=/") {
				appName = strings.Split(e, "=/")[1]
			} else if strings.Contains(e, "MESOS_TASK_ID=") {
				taskId = strings.Split(e, "=")[1]
			} else if strings.Contains(e, "PORTS=") {
				ports = strings.Split(e, "=")[1]
			}
		}
		if appName != "" && taskId != "" && ports != "" {
			mesoslock.Lock()
			M1[container.Name] = appName + " " + taskId + " " + "[" + ports + "]"
			mesoslock.Unlock()
		}
	}
}
<commit_msg>time update<commit_after>package utils

import (
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/Jeffail/gabs"
	log "github.com/cihub/seelog"
	"github.com/fsouza/go-dockerclient"
	"io/ioutil"
	"net"
	"os"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	countFile = "/tmp/logspout/logspout.json"
)

var (
	UUID        string
	M1          map[string]string
	IP          string
	Hostname    string
	UserId      string
	ClusterId   string
	counter     map[string]int64
	counterlock sync.Mutex
	mesoslock   sync.Mutex
)

func getPort(ports string) string {
	reg := regexp.MustCompile("\\[|\\]")
	strs := strings.Split(reg.ReplaceAllString(ports, ""), "-")
	startPort, serr := strconv.Atoi(strs[0])
	endPort, eerr := strconv.Atoi(strs[1])
	if serr != nil || eerr != nil {
		return "[]"
	}
	var port []string
	for i := startPort; i <= endPort; i++ {
		port = append(port, strconv.Itoa(i))
	}
	return "[" + strings.Join(port, ",") + "]"

}

type Message struct {
	FrameWorks []struct {
		Executors []struct {
			Container string `json:"container"`
			Tasks     []struct {
				SlaveId   string `json:"slave_id"`
				State     string `json:"state"`
				Name      string `json:"name"`
				Id        string `json:"id"`
				Resources struct {
					Ports string `json:"ports"`
				} `json:"resources"`
			} `json:"tasks"`
		} `json:"executors"`
	} `json:"frameworks"`
	HostName string `json:"hostname"`
}

func init() {
	counterlock, mesoslock = sync.Mutex{}, sync.Mutex{}
	M1 = make(map[string]string)
	loadCounter()
	UUID = os.Getenv("HOST_ID")
	if UUID == "" {
		log.Error("cat't found uuid")
		os.Exit(0)
	}
	UserId = os.Getenv("USER_ID")
	if UserId == "" {
		log.Error("cat't found userid")
		os.Exit(0)
	}
	ClusterId = os.Getenv("CLUSTER_ID")
	if ClusterId == "" {
		log.Error("cat't found clusterid")
		os.Exit(0)
	}
	Hostname, _ = os.Hostname()
	IP, _ = GetIp()
	M1 = getCnames()
}

func Run() {
	timer := time.NewTicker(5 * time.Second)
	for {
		select {
		case <-timer.C:
			//getMesosInfo(mesoslock)
			persistenCounter()
		}
	}
}

func getMesosInfo(lock *sync.Mutex) {
	data, err := HttpGet("http://" + IP + ":5051/slave(1)/state.json")
	if err == nil {
		mg := getCnames()
		var m Message
		json.Unmarshal([]byte(data), &m)
		if len(m.FrameWorks) > 0 {
			for _, fw := range m.FrameWorks {
				if len(fw.Executors) > 0 {
					for _, ex := range fw.Executors {
						if len(ex.Tasks) > 0 {
							for _, ts := range ex.Tasks {
								mcn := "/mesos-" + ts.SlaveId + "." + ex.Container
								mg[mcn] = ts.Name + " " +
									ts.Id + " " +
									getPort(ts.Resources.Ports)
							}
						}
					}
				}
			}
		}
		lock.Lock()
		M1 = mg
		lock.Unlock()
	}
	//log.Debug("get mesos json: ", err, M1)
}

func GetIp() (ip string, err error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}
	for _, iface := range ifaces {
		if iface.Flags&net.FlagUp == 0 {
			continue // interface down
		}
		if iface.Flags&net.FlagLoopback != 0 {
			continue // loopback interface
		}
		addrs, err := iface.Addrs()
		if err != nil {
			return "", err
		}
		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			}
			if ip == nil || ip.IsLoopback() {
				continue
			}
			ip = ip.To4()
			if ip == nil {
				continue // not an ipv4 address
			}
			return ip.String(), nil
		}
	}
	return "", errors.New("are you connected to the network?")
}

func ConnTCP(address string) (net.Conn, error) {
	raddr, err := net.ResolveTCPAddr("tcp", address)
	if err != nil {
		return nil, err
	}
	conn, err := net.DialTCP("tcp", nil, raddr)
	if err != nil {
		return nil, err
	}
	return conn, nil
}

func ConnTLS(address string) (net.Conn, error) {
	cert, err := tls.LoadX509KeyPair("/root/ssl/client.pem", "/root/ssl/client.key")
	if err != nil {
		return nil, err
	}
	config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
	conn, err := tls.Dial("tcp", address, &config)
	if err != nil {
		return nil, err
	}
	return conn, nil
}

func getCnames() map[string]string {
	cnames := os.Getenv("CNAMES")
	if cnames != "" {
		cmap := make(map[string]string)
		ca := strings.Split(cnames, ",")
		for _, cname := range ca {
			rname := strings.Replace(cname, "/", "", 1)
			cmap[cname] = rname + " " + rname + " " + "[]"
		}
		return cmap
	}
	return make(map[string]string)
}

func SendMessage(cn, msg string, d *docker.Container) string {
	counter[d.ID]++
	t := time.Now()
	//timestr := t.Format("2006-01-02T15:04:05.895+08:00")
	timestr := t.Format(time.RFC3339Nano)
	logmsg := timestr + " " +
		UserId + " " +
		fmt.Sprint(counter[d.ID]) + " " +
		ClusterId + " " +
		UUID + " " +
		IP + " " +
		Hostname + " " +
		cn + " " +
		msg
	return logmsg
}

func loadCounter() {
	counter = make(map[string]int64)
	buf, err := ioutil.ReadFile(countFile)
	if err == nil {
		json, err := gabs.ParseJSON(buf)
		if err == nil {
			m, err := json.ChildrenMap()
			if err == nil {
				for k, v := range m {
					if reflect.TypeOf(v.Data()).String() == "float64" {
						counter[k] = int64(v.Data().(float64))
					}
				}
				log.Debug("load container counter: ", json)
			} else {
				log.Error("counter childrenmap err: ", err)
			}
		} else {
			log.Error("counter str to json err: ", err)
		}
	} else {
		log.Debug("not found counter file: ", err)
	}
}

func persistenCounter() {
	if len(counter) > 0 {
		json := gabs.New()
		counterlock.Lock()
		for k, v := range counter {
			json.Set(v, k)
		}
		counterlock.Unlock()
		err := ioutil.WriteFile(countFile, json.Bytes(), 0x644)
		if err != nil {
			log.Error("persisten counter failed: ", err)
		}
	}
}

func DeleteCounter(client *docker.Client) {
	containers, err := client.ListContainers(docker.ListContainersOptions{})
	if err == nil {
		copycounter := counter
		for _, container := range containers {
			_, ok := copycounter[container.ID]
			if ok {
				delete(copycounter, container.ID)
			}
		}

		counterlock.Lock()
		for k, _ := range copycounter {
			delete(counter, k)
		}
		counterlock.Unlock()
	}
}

func DeleteM1(cname string) {
	if _, ok := getCnames()[cname]; !ok {
		mesoslock.Lock()
		delete(M1, cname)
		mesoslock.Unlock()
	}
}

func ReceiveContainer(container *docker.Container) {
	env := container.Config.Env
	appName := ""
	taskId := ""
	ports := ""
	if len(env) > 0 {
		for _, e := range env {
			if strings.Contains(e, "MARATHON_APP_ID=/") {
				appName = strings.Split(e, "=/")[1]
			} else if strings.Contains(e, "MESOS_TASK_ID=") {
				taskId = strings.Split(e, "=")[1]
			} else if strings.Contains(e, "PORTS=") {
				ports = strings.Split(e, "=")[1]
			}
		}
		if appName != "" && taskId != "" && ports != "" {
			mesoslock.Lock()
			M1[container.Name] = appName + " " + taskId + " " + "[" + ports + "]"
			mesoslock.Unlock()
		}
	}
}
<|endoftext|>


<commit_before>package xlsxtra_test

import (
	"fmt"
	"testing"

	"github.com/stanim/xlsxtra"
)

func TestSplitCoord(t *testing.T) {
	column, row, err := xlsxtra.SplitCoord("AA11")
	if err != nil {
		t.Fatal(err)
	}
	if column != "AA" || row != 11 {
		t.Fatalf("expected \"AA\" and 11; got %q and %d",
			column, row)
	}
	column, row, err = xlsxtra.SplitCoord("A0")
	if err == nil {
		t.Fatal("expected error")
	}
}

func Example() {
	fmt.Println(xlsxtra.ColStr[26], xlsxtra.StrCol["AA"])
	// Output: Z 27
}

func ExampleRangeBounds() {
	fmt.Println(xlsxtra.RangeBounds("A1:E6"))
	fmt.Println(xlsxtra.RangeBounds("$A$1:$E$6"))
	// invalid: no column name given
	fmt.Println(xlsxtra.RangeBounds("11:E6"))
	// invalid: row zero does not exist
	fmt.Println(xlsxtra.RangeBounds("A0:E6"))
	// Output:
	// 1 1 5 6 <nil>
	// 1 1 5 6 <nil>
	// 0 0 0 0 Invalid range "11:E6"
	// 0 0 0 0 Invalid range "A0:E6"
}
<commit_msg>fix ineffassign<commit_after>package xlsxtra_test

import (
	"fmt"
	"testing"

	"github.com/stanim/xlsxtra"
)

func TestSplitCoord(t *testing.T) {
	column, row, err := xlsxtra.SplitCoord("AA11")
	if err != nil {
		t.Fatal(err)
	}
	if column != "AA" || row != 11 {
		t.Fatalf("expected \"AA\" and 11; got %q and %d",
			column, row)
	}
	_, _, err = xlsxtra.SplitCoord("A0")
	if err == nil {
		t.Fatal("expected error")
	}
}

func Example() {
	fmt.Println(xlsxtra.ColStr[26], xlsxtra.StrCol["AA"])
	// Output: Z 27
}

func ExampleRangeBounds() {
	fmt.Println(xlsxtra.RangeBounds("A1:E6"))
	fmt.Println(xlsxtra.RangeBounds("$A$1:$E$6"))
	// invalid: no column name given
	fmt.Println(xlsxtra.RangeBounds("11:E6"))
	// invalid: row zero does not exist
	fmt.Println(xlsxtra.RangeBounds("A0:E6"))
	// Output:
	// 1 1 5 6 <nil>
	// 1 1 5 6 <nil>
	// 0 0 0 0 Invalid range "11:E6"
	// 0 0 0 0 Invalid range "A0:E6"
}
<|endoftext|>


<commit_before>package admin

import (
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/qor/qor"
	"github.com/qor/qor/resource"
	"github.com/qor/qor/roles"
	"github.com/qor/qor/utils"
)

type Meta struct {
	Name            string
	FieldName       string
	Label           string
	Type            string
	FormattedValuer func(interface{}, *qor.Context) interface{}
	Valuer          func(interface{}, *qor.Context) interface{}
	Setter          func(resource interface{}, metaValue *resource.MetaValue, context *qor.Context)
	Metas           []resource.Metaor
	Resource        *Resource
	Collection      interface{}
	GetCollection   func(interface{}, *qor.Context) [][]string
	Permission      *roles.Permission
	resource.Meta

	baseResource *Resource
}

func (meta *Meta) GetMetas() []resource.Metaor {
	if len(meta.Metas) > 0 {
		return meta.Metas
	} else if meta.Resource == nil {
		return []resource.Metaor{}
	} else {
		return meta.Resource.GetMetas([]string{})
	}
}

func (meta *Meta) GetResource() resource.Resourcer {
	return meta.Resource
}

func (meta *Meta) DBName() string {
	if meta.FieldStruct != nil {
		return meta.FieldStruct.DBName
	}
	return ""
}

func getField(fields []*gorm.StructField, name string) (*gorm.StructField, bool) {
	for _, field := range fields {
		if field.Name == name || field.DBName == name {
			return field, true
		}
	}
	return nil, false
}

func (meta *Meta) setBaseResource(base *Resource) {
	res := meta.Resource
	res.base = base

	findOneHandler := res.FindOneHandler
	res.FindOneHandler = func(value interface{}, metaValues *resource.MetaValues, context *qor.Context) error {
		if primryKey := res.getPrimaryKeyFromParams(context.Request); primryKey != "" {
			clone := context.Clone()
			baseValue := base.NewStruct()
			base.FindOneHandler(baseValue, nil, clone)
			sql := fmt.Sprintf("%v = ?", res.PrimaryDBName())
			return context.GetDB().Model(baseValue).Where(sql, primryKey).Related(value).Error
		}
		return findOneHandler(value, metaValues, context)
	}

	res.FindManyHandler = func(value interface{}, context *qor.Context) error {
		clone := context.Clone()
		baseValue := base.NewStruct()
		base.FindOneHandler(baseValue, nil, clone)
		return context.GetDB().Model(baseValue).Related(value).Error
	}

	saveHandle := res.SaveHandler
	res.SaveHandler = func(value interface{}, context *qor.Context) error {
		return saveHandle(value, context)
	}

	deleteHandle := res.DeleteHandler
	res.DeleteHandler = func(value interface{}, context *qor.Context) error {
		return deleteHandle(value, context)
	}
}

func (meta *Meta) updateMeta() {
	meta.Meta = resource.Meta{
		Name:            meta.Name,
		FieldName:       meta.FieldName,
		Setter:          meta.Setter,
		FormattedValuer: meta.FormattedValuer,
		Valuer:          meta.Valuer,
		Permission:      meta.Permission,
		Resource:        meta.baseResource,
	}

	meta.PreInitialize()
	if meta.FieldStruct != nil {
		if injector, ok := reflect.New(meta.FieldStruct.Struct.Type).Interface().(resource.ConfigureMetaBeforeInitializeInterface); ok {
			injector.ConfigureQorMetaBeforeInitialize(meta)
		}
	}

	meta.Initialize()

	if meta.Label == "" {
		meta.Label = utils.HumanizeString(meta.Name)
	}

	var fieldType reflect.Type
	var hasColumn = meta.FieldStruct != nil

	if hasColumn {
		fieldType = meta.FieldStruct.Struct.Type
		for fieldType.Kind() == reflect.Ptr {
			fieldType = fieldType.Elem()
		}
	}

	// Set Meta Type
	if meta.Type == "" && hasColumn {
		if relationship := meta.FieldStruct.Relationship; relationship != nil {
			if relationship.Kind == "has_one" {
				meta.Type = "single_edit"
			} else if relationship.Kind == "has_many" {
				meta.Type = "collection_edit"
			} else if relationship.Kind == "belongs_to" {
				meta.Type = "select_one"
			} else if relationship.Kind == "many_to_many" {
				meta.Type = "select_many"
			}
		} else {
			switch fieldType.Kind().String() {
			case "string":
				var tag = meta.FieldStruct.Tag
				if size, ok := utils.ParseTagOption(tag.Get("sql"))["SIZE"]; ok {
					if i, _ := strconv.Atoi(size); i > 255 {
						meta.Type = "text"
					} else {
						meta.Type = "string"
					}
				} else if text, ok := utils.ParseTagOption(tag.Get("sql"))["TYPE"]; ok && text == "text" {
					meta.Type = "text"
				} else {
					meta.Type = "string"
				}
			case "bool":
				meta.Type = "checkbox"
			default:
				if regexp.MustCompile(`^(.*)?(u)?(int)(\d+)?`).MatchString(fieldType.Kind().String()) {
					meta.Type = "number"
				} else if regexp.MustCompile(`^(.*)?(float)(\d+)?`).MatchString(fieldType.Kind().String()) {
					meta.Type = "float"
				} else if _, ok := reflect.New(fieldType).Interface().(*time.Time); ok {
					meta.Type = "datetime"
				}
			}
		}
	}

	{ // Set Meta Resource
		if meta.Resource == nil {
			if hasColumn && (meta.FieldStruct.Relationship != nil) {
				var result interface{}
				if fieldType.Kind() == reflect.Struct {
					result = reflect.New(fieldType).Interface()
				} else if fieldType.Kind() == reflect.Slice {
					refelectType := fieldType.Elem()
					for refelectType.Kind() == reflect.Ptr {
						refelectType = refelectType.Elem()
					}
					result = reflect.New(refelectType).Interface()
				}

				res := meta.baseResource.GetAdmin().NewResource(result)
				res.configure()
				meta.Resource = res
			}
		}

		if meta.Resource != nil {
			meta.setBaseResource(meta.baseResource)
		}
	}

	scope := &gorm.Scope{Value: meta.baseResource.Value}
	scopeField, _ := scope.FieldByName(meta.GetFieldName())

	{ // Format Meta FormattedValueOf
		if meta.FormattedValuer == nil {
			if meta.Type == "select_one" {
				meta.SetFormattedValuer(func(value interface{}, context *qor.Context) interface{} {
					return utils.Stringify(meta.GetValuer()(value, context))
				})
			} else if meta.Type == "select_many" {
				meta.SetFormattedValuer(func(value interface{}, context *qor.Context) interface{} {
					reflectValue := reflect.Indirect(reflect.ValueOf(meta.GetValuer()(value, context)))
					var results []string
					for i := 0; i < reflectValue.Len(); i++ {
						results = append(results, utils.Stringify(reflectValue.Index(i).Interface()))
					}
					return results
				})
			}
		}
	}

	{ // Format Meta Collection
		if meta.Collection != nil {
			if maps, ok := meta.Collection.([]string); ok {
				meta.GetCollection = func(interface{}, *qor.Context) (results [][]string) {
					for _, value := range maps {
						results = append(results, []string{value, value})
					}
					return
				}
			} else if maps, ok := meta.Collection.([][]string); ok {
				meta.GetCollection = func(interface{}, *qor.Context) [][]string {
					return maps
				}
			} else if f, ok := meta.Collection.(func(interface{}, *qor.Context) [][]string); ok {
				meta.GetCollection = f
			} else {
				utils.ExitWithMsg("Unsupported Collection format for meta %v of resource %v", meta.Name, reflect.TypeOf(meta.baseResource.Value))
			}
		} else if meta.Type == "select_one" || meta.Type == "select_many" {
			if scopeField.Relationship != nil {
				fieldType := scopeField.StructField.Struct.Type
				if fieldType.Kind() == reflect.Slice {
					fieldType = fieldType.Elem()
				}

				meta.GetCollection = func(value interface{}, context *qor.Context) (results [][]string) {
					values := reflect.New(reflect.SliceOf(fieldType)).Interface()
					context.GetDB().Find(values)
					reflectValues := reflect.Indirect(reflect.ValueOf(values))
					for i := 0; i < reflectValues.Len(); i++ {
						scope := scope.New(reflectValues.Index(i).Interface())
						primaryKey := fmt.Sprintf("%v", scope.PrimaryKeyValue())
						results = append(results, []string{primaryKey, utils.Stringify(reflectValues.Index(i).Interface())})
					}
					return
				}
			} else {
				utils.ExitWithMsg("%v meta type %v needs Collection", meta.Name, meta.Type)
			}
		}
	}

	meta.FieldName = meta.GetFieldName()

	if meta.FieldStruct != nil {
		if injector, ok := reflect.New(meta.FieldStruct.Struct.Type).Interface().(resource.ConfigureMetaInterface); ok {
			injector.ConfigureQorMeta(meta)
		}
	}
}
<commit_msg>Refactor FindOneHandler, FindManyHandler<commit_after>package admin

import (
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"time"

	"github.com/jinzhu/gorm"
	"github.com/qor/qor"
	"github.com/qor/qor/resource"
	"github.com/qor/qor/roles"
	"github.com/qor/qor/utils"
)

type Meta struct {
	Name            string
	FieldName       string
	Label           string
	Type            string
	FormattedValuer func(interface{}, *qor.Context) interface{}
	Valuer          func(interface{}, *qor.Context) interface{}
	Setter          func(resource interface{}, metaValue *resource.MetaValue, context *qor.Context)
	Metas           []resource.Metaor
	Resource        *Resource
	Collection      interface{}
	GetCollection   func(interface{}, *qor.Context) [][]string
	Permission      *roles.Permission
	resource.Meta

	baseResource *Resource
}

func (meta *Meta) GetMetas() []resource.Metaor {
	if len(meta.Metas) > 0 {
		return meta.Metas
	} else if meta.Resource == nil {
		return []resource.Metaor{}
	} else {
		return meta.Resource.GetMetas([]string{})
	}
}

func (meta *Meta) GetResource() resource.Resourcer {
	return meta.Resource
}

func (meta *Meta) DBName() string {
	if meta.FieldStruct != nil {
		return meta.FieldStruct.DBName
	}
	return ""
}

func getField(fields []*gorm.StructField, name string) (*gorm.StructField, bool) {
	for _, field := range fields {
		if field.Name == name || field.DBName == name {
			return field, true
		}
	}
	return nil, false
}

func (meta *Meta) setBaseResource(base *Resource) {
	res := meta.Resource
	res.base = base

	res.FindOneHandler = func(value interface{}, metaValues *resource.MetaValues, context *qor.Context) error {
		if primryKey := res.getPrimaryKeyFromParams(context.Request); primryKey != "" {
			clone := context.Clone()
			baseValue := base.NewStruct()
			if err := base.FindOneHandler(baseValue, nil, clone); err == nil {
				sql := fmt.Sprintf("%v = ?", res.PrimaryDBName())
				return context.GetDB().Model(baseValue).Where(sql, primryKey).Related(value).Error
			} else {
				return err
			}
		}
		return fmt.Errorf("no primary found for resource %v", res.ToParam())
	}

	res.FindManyHandler = func(value interface{}, context *qor.Context) error {
		clone := context.Clone()
		baseValue := base.NewStruct()
		if err := base.FindOneHandler(baseValue, nil, clone); err == nil {
			base.FindOneHandler(baseValue, nil, clone)
			return context.GetDB().Model(baseValue).Related(value).Error
		} else {
			return err
		}
	}

	saveHandle := res.SaveHandler
	res.SaveHandler = func(value interface{}, context *qor.Context) error {
		return saveHandle(value, context)
	}

	deleteHandle := res.DeleteHandler
	res.DeleteHandler = func(value interface{}, context *qor.Context) error {
		return deleteHandle(value, context)
	}
}

func (meta *Meta) updateMeta() {
	meta.Meta = resource.Meta{
		Name:            meta.Name,
		FieldName:       meta.FieldName,
		Setter:          meta.Setter,
		FormattedValuer: meta.FormattedValuer,
		Valuer:          meta.Valuer,
		Permission:      meta.Permission,
		Resource:        meta.baseResource,
	}

	meta.PreInitialize()
	if meta.FieldStruct != nil {
		if injector, ok := reflect.New(meta.FieldStruct.Struct.Type).Interface().(resource.ConfigureMetaBeforeInitializeInterface); ok {
			injector.ConfigureQorMetaBeforeInitialize(meta)
		}
	}

	meta.Initialize()

	if meta.Label == "" {
		meta.Label = utils.HumanizeString(meta.Name)
	}

	var fieldType reflect.Type
	var hasColumn = meta.FieldStruct != nil

	if hasColumn {
		fieldType = meta.FieldStruct.Struct.Type
		for fieldType.Kind() == reflect.Ptr {
			fieldType = fieldType.Elem()
		}
	}

	// Set Meta Type
	if meta.Type == "" && hasColumn {
		if relationship := meta.FieldStruct.Relationship; relationship != nil {
			if relationship.Kind == "has_one" {
				meta.Type = "single_edit"
			} else if relationship.Kind == "has_many" {
				meta.Type = "collection_edit"
			} else if relationship.Kind == "belongs_to" {
				meta.Type = "select_one"
			} else if relationship.Kind == "many_to_many" {
				meta.Type = "select_many"
			}
		} else {
			switch fieldType.Kind().String() {
			case "string":
				var tag = meta.FieldStruct.Tag
				if size, ok := utils.ParseTagOption(tag.Get("sql"))["SIZE"]; ok {
					if i, _ := strconv.Atoi(size); i > 255 {
						meta.Type = "text"
					} else {
						meta.Type = "string"
					}
				} else if text, ok := utils.ParseTagOption(tag.Get("sql"))["TYPE"]; ok && text == "text" {
					meta.Type = "text"
				} else {
					meta.Type = "string"
				}
			case "bool":
				meta.Type = "checkbox"
			default:
				if regexp.MustCompile(`^(.*)?(u)?(int)(\d+)?`).MatchString(fieldType.Kind().String()) {
					meta.Type = "number"
				} else if regexp.MustCompile(`^(.*)?(float)(\d+)?`).MatchString(fieldType.Kind().String()) {
					meta.Type = "float"
				} else if _, ok := reflect.New(fieldType).Interface().(*time.Time); ok {
					meta.Type = "datetime"
				}
			}
		}
	}

	{ // Set Meta Resource
		if meta.Resource == nil {
			if hasColumn && (meta.FieldStruct.Relationship != nil) {
				var result interface{}
				if fieldType.Kind() == reflect.Struct {
					result = reflect.New(fieldType).Interface()
				} else if fieldType.Kind() == reflect.Slice {
					refelectType := fieldType.Elem()
					for refelectType.Kind() == reflect.Ptr {
						refelectType = refelectType.Elem()
					}
					result = reflect.New(refelectType).Interface()
				}

				res := meta.baseResource.GetAdmin().NewResource(result)
				res.configure()
				meta.Resource = res
			}
		}

		if meta.Resource != nil {
			meta.setBaseResource(meta.baseResource)
		}
	}

	scope := &gorm.Scope{Value: meta.baseResource.Value}
	scopeField, _ := scope.FieldByName(meta.GetFieldName())

	{ // Format Meta FormattedValueOf
		if meta.FormattedValuer == nil {
			if meta.Type == "select_one" {
				meta.SetFormattedValuer(func(value interface{}, context *qor.Context) interface{} {
					return utils.Stringify(meta.GetValuer()(value, context))
				})
			} else if meta.Type == "select_many" {
				meta.SetFormattedValuer(func(value interface{}, context *qor.Context) interface{} {
					reflectValue := reflect.Indirect(reflect.ValueOf(meta.GetValuer()(value, context)))
					var results []string
					for i := 0; i < reflectValue.Len(); i++ {
						results = append(results, utils.Stringify(reflectValue.Index(i).Interface()))
					}
					return results
				})
			}
		}
	}

	{ // Format Meta Collection
		if meta.Collection != nil {
			if maps, ok := meta.Collection.([]string); ok {
				meta.GetCollection = func(interface{}, *qor.Context) (results [][]string) {
					for _, value := range maps {
						results = append(results, []string{value, value})
					}
					return
				}
			} else if maps, ok := meta.Collection.([][]string); ok {
				meta.GetCollection = func(interface{}, *qor.Context) [][]string {
					return maps
				}
			} else if f, ok := meta.Collection.(func(interface{}, *qor.Context) [][]string); ok {
				meta.GetCollection = f
			} else {
				utils.ExitWithMsg("Unsupported Collection format for meta %v of resource %v", meta.Name, reflect.TypeOf(meta.baseResource.Value))
			}
		} else if meta.Type == "select_one" || meta.Type == "select_many" {
			if scopeField.Relationship != nil {
				fieldType := scopeField.StructField.Struct.Type
				if fieldType.Kind() == reflect.Slice {
					fieldType = fieldType.Elem()
				}

				meta.GetCollection = func(value interface{}, context *qor.Context) (results [][]string) {
					values := reflect.New(reflect.SliceOf(fieldType)).Interface()
					context.GetDB().Find(values)
					reflectValues := reflect.Indirect(reflect.ValueOf(values))
					for i := 0; i < reflectValues.Len(); i++ {
						scope := scope.New(reflectValues.Index(i).Interface())
						primaryKey := fmt.Sprintf("%v", scope.PrimaryKeyValue())
						results = append(results, []string{primaryKey, utils.Stringify(reflectValues.Index(i).Interface())})
					}
					return
				}
			} else {
				utils.ExitWithMsg("%v meta type %v needs Collection", meta.Name, meta.Type)
			}
		}
	}

	meta.FieldName = meta.GetFieldName()

	if meta.FieldStruct != nil {
		if injector, ok := reflect.New(meta.FieldStruct.Struct.Type).Interface().(resource.ConfigureMetaInterface); ok {
			injector.ConfigureQorMeta(meta)
		}
	}
}
<|endoftext|>


<commit_before>package ground

import (
	"fmt"

	"github.com/pkg/errors"
	rbac "k8s.io/api/rbac/v1beta1"
	storage "k8s.io/api/storage/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	clientset "k8s.io/client-go/kubernetes"

	v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	openstack_project "github.com/sapcc/kubernikus/pkg/client/openstack/project"
	"github.com/sapcc/kubernikus/pkg/controller/config"
	"github.com/sapcc/kubernikus/pkg/controller/ground/bootstrap"
	"github.com/sapcc/kubernikus/pkg/controller/ground/bootstrap/dns"
	"github.com/sapcc/kubernikus/pkg/controller/ground/bootstrap/gpu"
	"github.com/sapcc/kubernikus/pkg/util"
)

func SeedKluster(clients config.Clients, factories config.Factories, kluster *v1.Kluster) error {
	kubernetes, err := clients.Satellites.ClientFor(kluster)
	if err != nil {
		return err
	}

	if err := SeedAllowBootstrapTokensToPostCSRs(kubernetes); err != nil {
		return errors.Wrap(err, "seed allow bootstrap tokens to post CSRs")
	}
	if err := SeedAutoApproveNodeBootstrapTokens(kubernetes); err != nil {
		return errors.Wrap(err, "seed auto approve node bootstrap tokens")
	}
	if err := SeedAutoRenewalNodeCertificates(kubernetes); err != nil {
		return errors.Wrap(err, "seed auto renewal node certificates")
	}
	if err := SeedKubernikusAdmin(kubernetes); err != nil {
		return errors.Wrap(err, "seed kubernikus admin")
	}
	if err := SeedKubernikusMember(kubernetes); err != nil {
		return errors.Wrap(err, "seed kubernikus member")
	}
	if !kluster.Spec.NoCloud {
		openstack, err := factories.Openstack.ProjectAdminClientFor(kluster.Account())
		if err != nil {
			return err
		}
		if err := SeedCinderStorageClasses(kubernetes, openstack); err != nil {
			return errors.Wrap(err, "seed cinder storage classes")
		}
	}
	if err := SeedAllowCertificateControllerToDeleteCSRs(kubernetes); err != nil {
		return errors.Wrap(err, "seed allow certificate controller to delete CSRs")
	}
	if err := SeedAllowApiserverToAccessKubeletAPI(kubernetes); err != nil {
		return errors.Wrap(err, "seed allow apiserver access to kubelet api")
	}
	if ok, _ := util.KlusterVersionConstraint(kluster, ">= 1.16"); ok {
		if err := dns.SeedCoreDNS116(kubernetes, "", "", kluster.Spec.DNSDomain, kluster.Spec.DNSAddress); err != nil {
			return errors.Wrap(err, "seed coredns")
		}
	} else if ok, _ := util.KlusterVersionConstraint(kluster, ">= 1.13"); ok {
		if err := dns.SeedCoreDNS(kubernetes, "", "", kluster.Spec.DNSDomain, kluster.Spec.DNSAddress); err != nil {
			return errors.Wrap(err, "seed coredns")
		}
	} else {
		if err := dns.SeedKubeDNS(kubernetes, "", "", kluster.Spec.DNSDomain, kluster.Spec.DNSAddress); err != nil {
			return errors.Wrap(err, "seed kubedns")
		}
	}

	if ok, _ := util.KlusterVersionConstraint(kluster, ">= 1.10"); ok {
		if err := gpu.SeedGPUSupport(kubernetes); err != nil {
			return errors.Wrap(err, "seed GPU support")
		}
	}

	if err := SeedOpenStackClusterRoleBindings(kubernetes); err != nil {
		return errors.Wrap(err, "seed openstack cluster role bindings")
	}

	return nil
}

func SeedCinderStorageClasses(client clientset.Interface, openstack openstack_project.ProjectClient) error {
	if err := createStorageClass(client, "cinder-default", "", true); err != nil {
		return err
	}

	metadata, err := openstack.GetMetadata()
	if err != nil {
		return err
	}

	for _, avz := range metadata.AvailabilityZones {
		name := fmt.Sprintf("cinder-zone-%s", avz.Name[len(avz.Name)-1:])
		if err := createStorageClass(client, name, avz.Name, false); err != nil {
			return err
		}
	}

	return nil
}

func createStorageClass(client clientset.Interface, name, avz string, isDefault bool) error {
	mode := storage.VolumeBindingImmediate

	if avz == "" {
		mode = storage.VolumeBindingWaitForFirstConsumer
	}

	storageClass := storage.StorageClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Provisioner:       "kubernetes.io/cinder",
		VolumeBindingMode: &mode,
	}

	if isDefault {
		storageClass.Annotations = map[string]string{
			"storageclass.kubernetes.io/is-default-class": "true",
		}
	}

	if avz != "" {
		storageClass.Parameters = map[string]string{
			"availability": avz,
		}
	}

	if _, err := client.StorageV1().StorageClasses().Create(&storageClass); err != nil {
		if !apierrors.IsAlreadyExists(err) {
			return fmt.Errorf("unable to create storage class: %v", err)
		}

		if _, err := client.StorageV1().StorageClasses().Update(&storageClass); err != nil {
			return fmt.Errorf("unable to update storage class: %v", err)
		}
	}

	return nil
}

func SeedKubernikusAdmin(client clientset.Interface) error {
	return bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:admin",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "cluster-admin",
		},
		Subjects: []rbac.Subject{
			{
				Kind: rbac.GroupKind,
				Name: "os:kubernetes_admin",
			},
		},
	})
}

func SeedKubernikusMember(client clientset.Interface) error {
	return bootstrap.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "kubernikus:member",
			Namespace: "default",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "Role",
			Name:     "edit",
		},
		Subjects: []rbac.Subject{
			{
				Kind: rbac.GroupKind,
				Name: "os:kubernetes_member",
			},
		},
	})
}

func SeedAllowBootstrapTokensToPostCSRs(client clientset.Interface) error {
	return bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:kubelet-bootstrap",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "system:node-bootstrapper",
		},
		Subjects: []rbac.Subject{
			{
				Kind: rbac.GroupKind,
				Name: "system:bootstrappers",
			},
		},
	})
}

func SeedAllowApiserverToAccessKubeletAPI(client clientset.Interface) error {
	return bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:apiserver-kubeletapi",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "system:kubelet-api-admin",
		},
		Subjects: []rbac.Subject{
			{
				Kind: rbac.UserKind,
				Name: "apiserver",
			},
		},
	})
}

// addresses https://github.com/kubernetes/kubernetes/issues/59351
func SeedAllowCertificateControllerToDeleteCSRs(client clientset.Interface) error {
	return bootstrap.CreateOrUpdateClusterRole(client, &rbac.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name: "system:controller:certificate-controller",
			Annotations: map[string]string{
				"rbac.authorization.kubernetes.io/autoupdate": "true",
			},
			Labels: map[string]string{
				"kubernetes.io/bootstrapping": "rbac-defaults",
			},
		},
		Rules: []rbac.PolicyRule{
			{
				Verbs:     []string{"delete", "get", "list", "watch"},
				APIGroups: []string{"certificates.k8s.io"},
				Resources: []string{"certificatesigningrequests"},
			},
			{
				Verbs:     []string{"update"},
				APIGroups: []string{"certificates.k8s.io"},
				Resources: []string{"certificatesigningrequests/approval", "certificatesigningrequests/status"},
			},
			{
				Verbs:     []string{"create"},
				APIGroups: []string{"authorization.k8s.io"},
				Resources: []string{"subjectaccessreviews"},
			},
			{
				Verbs:     []string{"create", "patch", "update"},
				APIGroups: []string{""}, //looks funny but is in the default rule ...
				Resources: []string{"events"},
			},
		},
	})
}

func SeedAutoApproveNodeBootstrapTokens(client clientset.Interface) error {
	err := bootstrap.CreateOrUpdateClusterRole(client, &rbac.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:approve-node-client-csr",
		},
		Rules: []rbac.PolicyRule{
			{
				Verbs:     []string{"create"},
				APIGroups: []string{"certificates.k8s.io"},
				Resources: []string{"certificatesigningrequests/nodeclient"},
			},
		},
	})
	if err != nil {
		return err
	}

	return bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:node-client-csr-autoapprove",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "kubernikus:approve-node-client-csr",
		},
		Subjects: []rbac.Subject{
			{
				Kind: "Group",
				Name: "system:bootstrappers",
			},
		},
	})
}

func SeedAutoRenewalNodeCertificates(client clientset.Interface) error {
	err := bootstrap.CreateOrUpdateClusterRole(client, &rbac.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name: "system:certificates.k8s.io:certificatesigningrequests:selfnodeclient",
		},
		Rules: []rbac.PolicyRule{
			{
				Verbs:     []string{"create"},
				APIGroups: []string{"certificates.k8s.io"},
				Resources: []string{"certificatesigningrequests/selfnodeclient"},
			},
		},
	})
	if err != nil {
		return err
	}

	return bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:auto-approve-renewals-for-nodes",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "system:certificates.k8s.io:certificatesigningrequests:selfnodeclient",
		},
		Subjects: []rbac.Subject{
			{
				APIGroup: rbac.GroupName,
				Kind:     "Group",
				Name:     "system:nodes",
			},
		},
	})
}

func SeedOpenStackClusterRoleBindings(client clientset.Interface) error {

	err := bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:openstack-kubernetes-admin",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "cluster-admin",
		},
		Subjects: []rbac.Subject{
			{
				Kind: "Group",
				Name: "openstack_role:kubernetes_admin",
			},
			{
				Kind: "User",
				// It is the marshall & b64enc of the protobuf message IDTokenSubject: https://github.com/dexidp/dex/blob/master/server/oauth2.go#L300
				// User ID: 00000000-0000-0000-0000-000000000001 ConnID: local
				Name: "CiQwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDESBWxvY2Fs",
				// For claims, we are using "sub" instead of "email" since some technical users missing emails
				// If we switch to email, we can directly use email as Name field above
			},
		},
	})

	if err != nil {
		return err
	}

	err = bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:openstack-kubernetes-member",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "view",
		},
		Subjects: []rbac.Subject{
			{
				Kind: "Group",
				Name: "openstack_role:kubernetes_member",
			},
		},
	})

	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Remove outdated role for certificate controller<commit_after>package ground

import (
	"fmt"

	"github.com/pkg/errors"
	rbac "k8s.io/api/rbac/v1beta1"
	storage "k8s.io/api/storage/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	clientset "k8s.io/client-go/kubernetes"

	v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	openstack_project "github.com/sapcc/kubernikus/pkg/client/openstack/project"
	"github.com/sapcc/kubernikus/pkg/controller/config"
	"github.com/sapcc/kubernikus/pkg/controller/ground/bootstrap"
	"github.com/sapcc/kubernikus/pkg/controller/ground/bootstrap/dns"
	"github.com/sapcc/kubernikus/pkg/controller/ground/bootstrap/gpu"
	"github.com/sapcc/kubernikus/pkg/util"
)

func SeedKluster(clients config.Clients, factories config.Factories, kluster *v1.Kluster) error {
	kubernetes, err := clients.Satellites.ClientFor(kluster)
	if err != nil {
		return err
	}

	if err := SeedAllowBootstrapTokensToPostCSRs(kubernetes); err != nil {
		return errors.Wrap(err, "seed allow bootstrap tokens to post CSRs")
	}
	if err := SeedAutoApproveNodeBootstrapTokens(kubernetes); err != nil {
		return errors.Wrap(err, "seed auto approve node bootstrap tokens")
	}
	if err := SeedAutoRenewalNodeCertificates(kubernetes); err != nil {
		return errors.Wrap(err, "seed auto renewal node certificates")
	}
	if err := SeedKubernikusAdmin(kubernetes); err != nil {
		return errors.Wrap(err, "seed kubernikus admin")
	}
	if err := SeedKubernikusMember(kubernetes); err != nil {
		return errors.Wrap(err, "seed kubernikus member")
	}
	if !kluster.Spec.NoCloud {
		openstack, err := factories.Openstack.ProjectAdminClientFor(kluster.Account())
		if err != nil {
			return err
		}
		if err := SeedCinderStorageClasses(kubernetes, openstack); err != nil {
			return errors.Wrap(err, "seed cinder storage classes")
		}
	}
	if err := SeedAllowApiserverToAccessKubeletAPI(kubernetes); err != nil {
		return errors.Wrap(err, "seed allow apiserver access to kubelet api")
	}
	if ok, _ := util.KlusterVersionConstraint(kluster, ">= 1.16"); ok {
		if err := dns.SeedCoreDNS116(kubernetes, "", "", kluster.Spec.DNSDomain, kluster.Spec.DNSAddress); err != nil {
			return errors.Wrap(err, "seed coredns")
		}
	} else if ok, _ := util.KlusterVersionConstraint(kluster, ">= 1.13"); ok {
		if err := dns.SeedCoreDNS(kubernetes, "", "", kluster.Spec.DNSDomain, kluster.Spec.DNSAddress); err != nil {
			return errors.Wrap(err, "seed coredns")
		}
	} else {
		if err := dns.SeedKubeDNS(kubernetes, "", "", kluster.Spec.DNSDomain, kluster.Spec.DNSAddress); err != nil {
			return errors.Wrap(err, "seed kubedns")
		}
	}

	if ok, _ := util.KlusterVersionConstraint(kluster, ">= 1.10"); ok {
		if err := gpu.SeedGPUSupport(kubernetes); err != nil {
			return errors.Wrap(err, "seed GPU support")
		}
	}

	if err := SeedOpenStackClusterRoleBindings(kubernetes); err != nil {
		return errors.Wrap(err, "seed openstack cluster role bindings")
	}

	return nil
}

func SeedCinderStorageClasses(client clientset.Interface, openstack openstack_project.ProjectClient) error {
	if err := createStorageClass(client, "cinder-default", "", true); err != nil {
		return err
	}

	metadata, err := openstack.GetMetadata()
	if err != nil {
		return err
	}

	for _, avz := range metadata.AvailabilityZones {
		name := fmt.Sprintf("cinder-zone-%s", avz.Name[len(avz.Name)-1:])
		if err := createStorageClass(client, name, avz.Name, false); err != nil {
			return err
		}
	}

	return nil
}

func createStorageClass(client clientset.Interface, name, avz string, isDefault bool) error {
	mode := storage.VolumeBindingImmediate

	if avz == "" {
		mode = storage.VolumeBindingWaitForFirstConsumer
	}

	storageClass := storage.StorageClass{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Provisioner:       "kubernetes.io/cinder",
		VolumeBindingMode: &mode,
	}

	if isDefault {
		storageClass.Annotations = map[string]string{
			"storageclass.kubernetes.io/is-default-class": "true",
		}
	}

	if avz != "" {
		storageClass.Parameters = map[string]string{
			"availability": avz,
		}
	}

	if _, err := client.StorageV1().StorageClasses().Create(&storageClass); err != nil {
		if !apierrors.IsAlreadyExists(err) {
			return fmt.Errorf("unable to create storage class: %v", err)
		}

		if _, err := client.StorageV1().StorageClasses().Update(&storageClass); err != nil {
			return fmt.Errorf("unable to update storage class: %v", err)
		}
	}

	return nil
}

func SeedKubernikusAdmin(client clientset.Interface) error {
	return bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:admin",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "cluster-admin",
		},
		Subjects: []rbac.Subject{
			{
				Kind: rbac.GroupKind,
				Name: "os:kubernetes_admin",
			},
		},
	})
}

func SeedKubernikusMember(client clientset.Interface) error {
	return bootstrap.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "kubernikus:member",
			Namespace: "default",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "Role",
			Name:     "edit",
		},
		Subjects: []rbac.Subject{
			{
				Kind: rbac.GroupKind,
				Name: "os:kubernetes_member",
			},
		},
	})
}

func SeedAllowBootstrapTokensToPostCSRs(client clientset.Interface) error {
	return bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:kubelet-bootstrap",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "system:node-bootstrapper",
		},
		Subjects: []rbac.Subject{
			{
				Kind: rbac.GroupKind,
				Name: "system:bootstrappers",
			},
		},
	})
}

func SeedAllowApiserverToAccessKubeletAPI(client clientset.Interface) error {
	return bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:apiserver-kubeletapi",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "system:kubelet-api-admin",
		},
		Subjects: []rbac.Subject{
			{
				Kind: rbac.UserKind,
				Name: "apiserver",
			},
		},
	})
}

func SeedAutoApproveNodeBootstrapTokens(client clientset.Interface) error {
	err := bootstrap.CreateOrUpdateClusterRole(client, &rbac.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:approve-node-client-csr",
		},
		Rules: []rbac.PolicyRule{
			{
				Verbs:     []string{"create"},
				APIGroups: []string{"certificates.k8s.io"},
				Resources: []string{"certificatesigningrequests/nodeclient"},
			},
		},
	})
	if err != nil {
		return err
	}

	return bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:node-client-csr-autoapprove",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "kubernikus:approve-node-client-csr",
		},
		Subjects: []rbac.Subject{
			{
				Kind: "Group",
				Name: "system:bootstrappers",
			},
		},
	})
}

func SeedAutoRenewalNodeCertificates(client clientset.Interface) error {
	err := bootstrap.CreateOrUpdateClusterRole(client, &rbac.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name: "system:certificates.k8s.io:certificatesigningrequests:selfnodeclient",
		},
		Rules: []rbac.PolicyRule{
			{
				Verbs:     []string{"create"},
				APIGroups: []string{"certificates.k8s.io"},
				Resources: []string{"certificatesigningrequests/selfnodeclient"},
			},
		},
	})
	if err != nil {
		return err
	}

	return bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:auto-approve-renewals-for-nodes",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "system:certificates.k8s.io:certificatesigningrequests:selfnodeclient",
		},
		Subjects: []rbac.Subject{
			{
				APIGroup: rbac.GroupName,
				Kind:     "Group",
				Name:     "system:nodes",
			},
		},
	})
}

func SeedOpenStackClusterRoleBindings(client clientset.Interface) error {

	err := bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:openstack-kubernetes-admin",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "cluster-admin",
		},
		Subjects: []rbac.Subject{
			{
				Kind: "Group",
				Name: "openstack_role:kubernetes_admin",
			},
			{
				Kind: "User",
				// It is the marshall & b64enc of the protobuf message IDTokenSubject: https://github.com/dexidp/dex/blob/master/server/oauth2.go#L300
				// User ID: 00000000-0000-0000-0000-000000000001 ConnID: local
				Name: "CiQwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDESBWxvY2Fs",
				// For claims, we are using "sub" instead of "email" since some technical users missing emails
				// If we switch to email, we can directly use email as Name field above
			},
		},
	})

	if err != nil {
		return err
	}

	err = bootstrap.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name: "kubernikus:openstack-kubernetes-member",
		},
		RoleRef: rbac.RoleRef{
			APIGroup: rbac.GroupName,
			Kind:     "ClusterRole",
			Name:     "view",
		},
		Subjects: []rbac.Subject{
			{
				Kind: "Group",
				Name: "openstack_role:kubernetes_member",
			},
		},
	})

	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<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

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

	"github.com/google/exposure-notifications-verification-server/pkg/cache"
	"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"

	"github.com/gorilla/mux"
)

// LoadCurrentRealm loads the selected realm from the cache to the context
func LoadCurrentRealm(ctx context.Context, cacher cache.Cacher, db *database.Database, h *render.Renderer) mux.MiddlewareFunc {
	logger := logging.FromContext(ctx).Named("middleware.RequireRealm")

	cacheTTL := 5 * time.Minute

	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 {
				controller.MissingSession(w, r, h)
				return
			}

			realmID := controller.RealmIDFromSession(session)
			if realmID == 0 {
				logger.Debugw("realm does not exist in session")
				// If no realm on session, continue serving.
				// If realm is non-optional the caller should RequireRealm or RequireAdmin.
				next.ServeHTTP(w, r)
				return
			}

			// Load the realm by using the cache to alleviate pressure on the database
			// layer.
			var realm database.Realm
			cacheKey := fmt.Sprintf("realms:by_id:%d", realmID)
			if err := cacher.Fetch(ctx, cacheKey, &realm, cacheTTL, func() (interface{}, error) {
				return db.FindRealm(realmID)
			}); err != nil {
				if database.IsNotFound(err) {
					logger.Debugw("realm does not exist")
					controller.MissingRealm(w, r, h)
					return
				}

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

			// Save the realm on the context.
			ctx = controller.WithRealm(ctx, &realm)
			*r = *r.WithContext(ctx)

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

// RequireRealm requires a realm to exist in the session. It also ensures the
// realm is set as currentRealm in the template map.
//
// Must come after:
//   LoadCurrentRealm to populate the current realm.
//   RequireAuth so that a user is set on the context.
func RequireRealm(ctx context.Context, h *render.Renderer) mux.MiddlewareFunc {
	logger := logging.FromContext(ctx).Named("middleware.RequireRealm")

	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
			}

			realm := controller.RealmFromContext(ctx)
			if realm == nil {
				controller.MissingRealm(w, r, h)
				return
			}

			if !user.CanViewRealm(realm.ID) {
				logger.Debugw("user cannot view realm")
				// Technically this is unauthorized, but we don't want to leak the
				// existence of a realm by returning a different error.
				controller.MissingRealm(w, r, h)
				return
			}

			if passwordRedirectRequired(ctx, user, realm) {
				controller.RedirectToChangePassword(w, r, h)
			}

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

// RequireRealmAdmin verifies the user is an admin of the current realm.
//
// Must come after:
//   LoadCurrentRealm to populate the current realm.
//   RequireAuth so that a user is set on the context.
func RequireRealmAdmin(ctx context.Context, h *render.Renderer) mux.MiddlewareFunc {
	logger := logging.FromContext(ctx).Named("middleware.RequireRealmAdmin")

	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
			}

			realm := controller.RealmFromContext(ctx)
			if realm == nil {
				controller.MissingRealm(w, r, h)
				return
			}

			if !user.CanAdminRealm(realm.ID) {
				logger.Debugw("user cannot manage realm")
				// Technically this is unauthorized, but we don't want to leak the
				// existence of a realm by returning a different error.
				controller.MissingRealm(w, r, h)
				return
			}

			if passwordRedirectRequired(ctx, user, realm) {
				controller.RedirectToChangePassword(w, r, h)
			}

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

func passwordRedirectRequired(ctx context.Context, user *database.User, realm *database.Realm) bool {
	err := checkRealmPasswordAge(user, realm)
	if err == nil {
		return false
	}
	session := controller.SessionFromContext(ctx)
	flash := controller.Flash(session)

	if err == errPasswordChangeRequired {
		flash.Error(strings.Title(err.Error() + "."))
		return true
	}

	if !controller.PasswordExpireWarnedFromSession(session) {
		controller.StorePasswordExpireWarned(session, true)
		flash.Warning(strings.Title(err.Error() + "."))
	}
	return false
}

var errPasswordChangeRequired = errors.New("password change required")

func checkRealmPasswordAge(user *database.User, realm *database.Realm) error {
	if realm.PasswordRotationPeriodDays <= 0 {
		return nil
	}

	now := time.Now().UTC()
	nextPasswordChange := user.PasswordChanged().Add(
		time.Hour * 24 * time.Duration(realm.PasswordRotationPeriodDays))

	if now.After(nextPasswordChange) {
		return errPasswordChangeRequired
	}

	if nextPasswordChange.Add(
		time.Hour * 24 * time.Duration(realm.PasswordRotationWarningDays)).
		After(now) {
		untilChange := nextPasswordChange.Sub(now).Hours()
		if daysUntilChange := int(untilChange / 24); daysUntilChange > 1 {
			return fmt.Errorf("password change required in %d days", daysUntilChange)
		}
		if hoursUntilChange := int(untilChange); hoursUntilChange > 1 {
			return fmt.Errorf("password change required in %d hours", hoursUntilChange)
		}
		return fmt.Errorf("password change required soon")
	}

	return nil
}
<commit_msg>fix some bad time math (#725)<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

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

	"github.com/google/exposure-notifications-verification-server/pkg/cache"
	"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"

	"github.com/gorilla/mux"
)

// LoadCurrentRealm loads the selected realm from the cache to the context
func LoadCurrentRealm(ctx context.Context, cacher cache.Cacher, db *database.Database, h *render.Renderer) mux.MiddlewareFunc {
	logger := logging.FromContext(ctx).Named("middleware.RequireRealm")

	cacheTTL := 5 * time.Minute

	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 {
				controller.MissingSession(w, r, h)
				return
			}

			realmID := controller.RealmIDFromSession(session)
			if realmID == 0 {
				logger.Debugw("realm does not exist in session")
				// If no realm on session, continue serving.
				// If realm is non-optional the caller should RequireRealm or RequireAdmin.
				next.ServeHTTP(w, r)
				return
			}

			// Load the realm by using the cache to alleviate pressure on the database
			// layer.
			var realm database.Realm
			cacheKey := fmt.Sprintf("realms:by_id:%d", realmID)
			if err := cacher.Fetch(ctx, cacheKey, &realm, cacheTTL, func() (interface{}, error) {
				return db.FindRealm(realmID)
			}); err != nil {
				if database.IsNotFound(err) {
					logger.Debugw("realm does not exist")
					controller.MissingRealm(w, r, h)
					return
				}

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

			// Save the realm on the context.
			ctx = controller.WithRealm(ctx, &realm)
			*r = *r.WithContext(ctx)

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

// RequireRealm requires a realm to exist in the session. It also ensures the
// realm is set as currentRealm in the template map.
//
// Must come after:
//   LoadCurrentRealm to populate the current realm.
//   RequireAuth so that a user is set on the context.
func RequireRealm(ctx context.Context, h *render.Renderer) mux.MiddlewareFunc {
	logger := logging.FromContext(ctx).Named("middleware.RequireRealm")

	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
			}

			realm := controller.RealmFromContext(ctx)
			if realm == nil {
				controller.MissingRealm(w, r, h)
				return
			}

			if !user.CanViewRealm(realm.ID) {
				logger.Debugw("user cannot view realm")
				// Technically this is unauthorized, but we don't want to leak the
				// existence of a realm by returning a different error.
				controller.MissingRealm(w, r, h)
				return
			}

			if passwordRedirectRequired(ctx, user, realm) {
				controller.RedirectToChangePassword(w, r, h)
			}

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

// RequireRealmAdmin verifies the user is an admin of the current realm.
//
// Must come after:
//   LoadCurrentRealm to populate the current realm.
//   RequireAuth so that a user is set on the context.
func RequireRealmAdmin(ctx context.Context, h *render.Renderer) mux.MiddlewareFunc {
	logger := logging.FromContext(ctx).Named("middleware.RequireRealmAdmin")

	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
			}

			realm := controller.RealmFromContext(ctx)
			if realm == nil {
				controller.MissingRealm(w, r, h)
				return
			}

			if !user.CanAdminRealm(realm.ID) {
				logger.Debugw("user cannot manage realm")
				// Technically this is unauthorized, but we don't want to leak the
				// existence of a realm by returning a different error.
				controller.MissingRealm(w, r, h)
				return
			}

			if passwordRedirectRequired(ctx, user, realm) {
				controller.RedirectToChangePassword(w, r, h)
			}

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

func passwordRedirectRequired(ctx context.Context, user *database.User, realm *database.Realm) bool {
	err := checkRealmPasswordAge(user, realm)
	if err == nil {
		return false
	}
	session := controller.SessionFromContext(ctx)
	flash := controller.Flash(session)

	if err == errPasswordChangeRequired {
		flash.Error(strings.Title(err.Error() + "."))
		return true
	}

	if !controller.PasswordExpireWarnedFromSession(session) {
		controller.StorePasswordExpireWarned(session, true)
		flash.Warning(strings.Title(err.Error() + "."))
	}
	return false
}

var errPasswordChangeRequired = errors.New("password change required")

func checkRealmPasswordAge(user *database.User, realm *database.Realm) error {
	if realm.PasswordRotationPeriodDays <= 0 {
		return nil
	}

	now := time.Now().UTC()
	nextPasswordChange := user.PasswordChanged().Add(
		time.Hour * 24 * time.Duration(realm.PasswordRotationPeriodDays))

	if now.After(nextPasswordChange) {
		return errPasswordChangeRequired
	}

	if time.Until(nextPasswordChange) <
		time.Hour*24*time.Duration(realm.PasswordRotationWarningDays) {
		untilChange := nextPasswordChange.Sub(now).Hours()
		if daysUntilChange := int(untilChange / 24); daysUntilChange > 1 {
			return fmt.Errorf("password change required in %d days", daysUntilChange)
		}
		if hoursUntilChange := int(untilChange); hoursUntilChange > 1 {
			return fmt.Errorf("password change required in %d hours", hoursUntilChange)
		}
		return fmt.Errorf("password change required soon")
	}

	return nil
}
<|endoftext|>