<commit_before>/*
 * Minio Cloud Storage, (C) 2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"bytes"
	"errors"
	"hash"
	"io"
	"sync"

	"github.com/klauspost/reedsolomon"
	"github.com/minio/sha256-simd"
	"golang.org/x/crypto/blake2b"
)

// newHashWriters - inititialize a slice of hashes for the disk count.
func newHashWriters(diskCount int, algo string) []hash.Hash {
	hashWriters := make([]hash.Hash, diskCount)
	for index := range hashWriters {
		hashWriters[index] = newHash(algo)
	}
	return hashWriters
}

// newHash - gives you a newly allocated hash depending on the input algorithm.
func newHash(algo string) (h hash.Hash) {
	switch algo {
	case "sha256":
		// sha256 checksum specially on ARM64 platforms or whenever
		// requested as dictated by `xl.json` entry.
		h = sha256.New()
	case "blake2b":
		// ignore the error, because New512 without a key never fails
		// New512 only returns a non-nil error, if the length of the passed
		// key > 64 bytes - but we use blake2b as hash fucntion (no key)
		h, _ = blake2b.New512(nil)
	// Add new hashes here.
	default:
		// Default to blake2b.
		// ignore the error, because New512 without a key never fails
		// New512 only returns a non-nil error, if the length of the passed
		// key > 64 bytes - but we use blake2b as hash fucntion (no key)
		h, _ = blake2b.New512(nil)
	}
	return h
}

// Hash buffer pool is a pool of reusable
// buffers used while checksumming a stream.
var hashBufferPool = sync.Pool{
	New: func() interface{} {
		b := make([]byte, readSizeV1)
		return &b
	},
}

// hashSum calculates the hash of the entire path and returns.
func hashSum(disk StorageAPI, volume, path string, writer hash.Hash) ([]byte, error) {
	// Fetch staging a new staging buffer from the pool.
	bufp := hashBufferPool.Get().(*[]byte)
	defer hashBufferPool.Put(bufp)

	// Copy entire buffer to writer.
	if err := copyBuffer(writer, disk, volume, path, *bufp); err != nil {
		return nil, err
	}

	// Return the final hash sum.
	return writer.Sum(nil), nil
}

// getDataBlockLen - get length of data blocks from encoded blocks.
func getDataBlockLen(enBlocks [][]byte, dataBlocks int) int {
	size := 0
	// Figure out the data block length.
	for _, block := range enBlocks[:dataBlocks] {
		size += len(block)
	}
	return size
}

// Writes all the data blocks from encoded blocks until requested
// outSize length. Provides a way to skip bytes until the offset.
func writeDataBlocks(dst io.Writer, enBlocks [][]byte, dataBlocks int, offset int64, length int64) (int64, error) {
	// Offset and out size cannot be negative.
	if offset < 0 || length < 0 {
		return 0, traceError(errUnexpected)
	}

	// Do we have enough blocks?
	if len(enBlocks) < dataBlocks {
		return 0, traceError(reedsolomon.ErrTooFewShards)
	}

	// Do we have enough data?
	if int64(getDataBlockLen(enBlocks, dataBlocks)) < length {
		return 0, traceError(reedsolomon.ErrShortData)
	}

	// Counter to decrement total left to write.
	write := length

	// Counter to increment total written.
	totalWritten := int64(0)

	// Write all data blocks to dst.
	for _, block := range enBlocks[:dataBlocks] {
		// Skip blocks until we have reached our offset.
		if offset >= int64(len(block)) {
			// Decrement offset.
			offset -= int64(len(block))
			continue
		} else {
			// Skip until offset.
			block = block[offset:]

			// Reset the offset for next iteration to read everything
			// from subsequent blocks.
			offset = 0
		}
		// We have written all the blocks, write the last remaining block.
		if write < int64(len(block)) {
			n, err := io.Copy(dst, bytes.NewReader(block[:write]))
			if err != nil {
				return 0, traceError(err)
			}
			totalWritten += n
			break
		}
		// Copy the block.
		n, err := io.Copy(dst, bytes.NewReader(block))
		if err != nil {
			return 0, traceError(err)
		}

		// Decrement output size.
		write -= n

		// Increment written.
		totalWritten += n
	}

	// Success.
	return totalWritten, nil
}

// chunkSize is roughly BlockSize/DataBlocks.
// chunkSize is calculated such that chunkSize*DataBlocks accommodates BlockSize bytes.
// So chunkSize*DataBlocks can be slightly larger than BlockSize if BlockSize is not divisible by
// DataBlocks. The extra space will have 0-padding.
func getChunkSize(blockSize int64, dataBlocks int) int64 {
	return (blockSize + int64(dataBlocks) - 1) / int64(dataBlocks)
}

// copyBuffer - copies from disk, volume, path to input writer until either EOF
// is reached at volume, path or an error occurs. A success copyBuffer returns
// err == nil, not err == EOF. Because copyBuffer is defined to read from path
// until EOF. It does not treat an EOF from ReadFile an error to be reported.
// Additionally copyBuffer stages through the provided buffer; otherwise if it
// has zero length, returns error.
func copyBuffer(writer io.Writer, disk StorageAPI, volume string, path string, buf []byte) error {
	// Error condition of zero length buffer.
	if buf != nil && len(buf) == 0 {
		return errors.New("empty buffer in readBuffer")
	}

	// Starting offset for Reading the file.
	startOffset := int64(0)

	// Read until io.EOF.
	for {
		n, err := disk.ReadFile(volume, path, startOffset, buf)
		if n > 0 {
			m, wErr := writer.Write(buf[:n])
			if wErr != nil {
				return wErr
			}
			if int64(m) != n {
				return io.ErrShortWrite
			}
		}
		if err != nil {
			if err == io.EOF || err == io.ErrUnexpectedEOF {
				break
			}
			return err
		}
		// Progress the offset.
		startOffset += n
	}

	// Success.
	return nil
}
<commit_msg>Fix typo in erasure-utils.go<commit_after>/*
 * Minio Cloud Storage, (C) 2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cmd

import (
	"bytes"
	"errors"
	"hash"
	"io"
	"sync"

	"github.com/klauspost/reedsolomon"
	"github.com/minio/sha256-simd"
	"golang.org/x/crypto/blake2b"
)

// newHashWriters - inititialize a slice of hashes for the disk count.
func newHashWriters(diskCount int, algo string) []hash.Hash {
	hashWriters := make([]hash.Hash, diskCount)
	for index := range hashWriters {
		hashWriters[index] = newHash(algo)
	}
	return hashWriters
}

// newHash - gives you a newly allocated hash depending on the input algorithm.
func newHash(algo string) (h hash.Hash) {
	switch algo {
	case "sha256":
		// sha256 checksum specially on ARM64 platforms or whenever
		// requested as dictated by `xl.json` entry.
		h = sha256.New()
	case "blake2b":
		// ignore the error, because New512 without a key never fails
		// New512 only returns a non-nil error, if the length of the passed
		// key > 64 bytes - but we use blake2b as hash function (no key)
		h, _ = blake2b.New512(nil)
	// Add new hashes here.
	default:
		// Default to blake2b.
		// ignore the error, because New512 without a key never fails
		// New512 only returns a non-nil error, if the length of the passed
		// key > 64 bytes - but we use blake2b as hash function (no key)
		h, _ = blake2b.New512(nil)
	}
	return h
}

// Hash buffer pool is a pool of reusable
// buffers used while checksumming a stream.
var hashBufferPool = sync.Pool{
	New: func() interface{} {
		b := make([]byte, readSizeV1)
		return &b
	},
}

// hashSum calculates the hash of the entire path and returns.
func hashSum(disk StorageAPI, volume, path string, writer hash.Hash) ([]byte, error) {
	// Fetch staging a new staging buffer from the pool.
	bufp := hashBufferPool.Get().(*[]byte)
	defer hashBufferPool.Put(bufp)

	// Copy entire buffer to writer.
	if err := copyBuffer(writer, disk, volume, path, *bufp); err != nil {
		return nil, err
	}

	// Return the final hash sum.
	return writer.Sum(nil), nil
}

// getDataBlockLen - get length of data blocks from encoded blocks.
func getDataBlockLen(enBlocks [][]byte, dataBlocks int) int {
	size := 0
	// Figure out the data block length.
	for _, block := range enBlocks[:dataBlocks] {
		size += len(block)
	}
	return size
}

// Writes all the data blocks from encoded blocks until requested
// outSize length. Provides a way to skip bytes until the offset.
func writeDataBlocks(dst io.Writer, enBlocks [][]byte, dataBlocks int, offset int64, length int64) (int64, error) {
	// Offset and out size cannot be negative.
	if offset < 0 || length < 0 {
		return 0, traceError(errUnexpected)
	}

	// Do we have enough blocks?
	if len(enBlocks) < dataBlocks {
		return 0, traceError(reedsolomon.ErrTooFewShards)
	}

	// Do we have enough data?
	if int64(getDataBlockLen(enBlocks, dataBlocks)) < length {
		return 0, traceError(reedsolomon.ErrShortData)
	}

	// Counter to decrement total left to write.
	write := length

	// Counter to increment total written.
	totalWritten := int64(0)

	// Write all data blocks to dst.
	for _, block := range enBlocks[:dataBlocks] {
		// Skip blocks until we have reached our offset.
		if offset >= int64(len(block)) {
			// Decrement offset.
			offset -= int64(len(block))
			continue
		} else {
			// Skip until offset.
			block = block[offset:]

			// Reset the offset for next iteration to read everything
			// from subsequent blocks.
			offset = 0
		}
		// We have written all the blocks, write the last remaining block.
		if write < int64(len(block)) {
			n, err := io.Copy(dst, bytes.NewReader(block[:write]))
			if err != nil {
				return 0, traceError(err)
			}
			totalWritten += n
			break
		}
		// Copy the block.
		n, err := io.Copy(dst, bytes.NewReader(block))
		if err != nil {
			return 0, traceError(err)
		}

		// Decrement output size.
		write -= n

		// Increment written.
		totalWritten += n
	}

	// Success.
	return totalWritten, nil
}

// chunkSize is roughly BlockSize/DataBlocks.
// chunkSize is calculated such that chunkSize*DataBlocks accommodates BlockSize bytes.
// So chunkSize*DataBlocks can be slightly larger than BlockSize if BlockSize is not divisible by
// DataBlocks. The extra space will have 0-padding.
func getChunkSize(blockSize int64, dataBlocks int) int64 {
	return (blockSize + int64(dataBlocks) - 1) / int64(dataBlocks)
}

// copyBuffer - copies from disk, volume, path to input writer until either EOF
// is reached at volume, path or an error occurs. A success copyBuffer returns
// err == nil, not err == EOF. Because copyBuffer is defined to read from path
// until EOF. It does not treat an EOF from ReadFile an error to be reported.
// Additionally copyBuffer stages through the provided buffer; otherwise if it
// has zero length, returns error.
func copyBuffer(writer io.Writer, disk StorageAPI, volume string, path string, buf []byte) error {
	// Error condition of zero length buffer.
	if buf != nil && len(buf) == 0 {
		return errors.New("empty buffer in readBuffer")
	}

	// Starting offset for Reading the file.
	startOffset := int64(0)

	// Read until io.EOF.
	for {
		n, err := disk.ReadFile(volume, path, startOffset, buf)
		if n > 0 {
			m, wErr := writer.Write(buf[:n])
			if wErr != nil {
				return wErr
			}
			if int64(m) != n {
				return io.ErrShortWrite
			}
		}
		if err != nil {
			if err == io.EOF || err == io.ErrUnexpectedEOF {
				break
			}
			return err
		}
		// Progress the offset.
		startOffset += n
	}

	// Success.
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"io/ioutil"
	"strings"

	"github.com/containers/image/directory"
	"github.com/containers/image/image"
	"github.com/containers/image/manifest"
	"github.com/containers/image/types"
	"github.com/urfave/cli"
)

// TODO(runcom): document args and usage
var layersCmd = cli.Command{
	Name:      "layers",
	Usage:     "Get layers of IMAGE-NAME",
	ArgsUsage: "IMAGE-NAME [LAYER...]",
	Action: func(c *cli.Context) error {
		if c.NArg() == 0 {
			return errors.New("please specify an image")
		}
		rawSource, err := parseImageSource(c, c.Args()[0], []string{
			// TODO: skopeo layers only supports these now
			// eventually we'll remove this command altogether...
			manifest.DockerV2Schema1SignedMediaType,
			manifest.DockerV2Schema1MediaType,
		})
		if err != nil {
			return err
		}
		src, err := image.FromSource(rawSource)
		if err != nil {
			rawSource.Close()
			return err
		}
		defer src.Close()

		blobDigests := c.Args().Tail()
		if len(blobDigests) == 0 {
			layers := src.LayerInfos()
			seenLayers := map[string]struct{}{}
			for _, info := range layers {
				if _, ok := seenLayers[info.Digest]; !ok {
					blobDigests = append(blobDigests, info.Digest)
					seenLayers[info.Digest] = struct{}{}
				}
			}
			configInfo := src.ConfigInfo()
			if configInfo.Digest != "" {
				blobDigests = append(blobDigests, configInfo.Digest)
			}
		}

		tmpDir, err := ioutil.TempDir(".", "layers-")
		if err != nil {
			return err
		}
		tmpDirRef, err := directory.NewReference(tmpDir)
		if err != nil {
			return err
		}
		dest, err := tmpDirRef.NewImageDestination(nil)
		if err != nil {
			return err
		}
		defer dest.Close()

		for _, digest := range blobDigests {
			if !strings.HasPrefix(digest, "sha256:") {
				digest = "sha256:" + digest
			}
			r, blobSize, err := rawSource.GetBlob(digest)
			if err != nil {
				return err
			}
			if _, err := dest.PutBlob(r, types.BlobInfo{Digest: digest, Size: blobSize}); err != nil {
				r.Close()
				return err
			}
			r.Close()
		}

		manifest, _, err := src.Manifest()
		if err != nil {
			return err
		}
		if err := dest.PutManifest(manifest); err != nil {
			return err
		}

		if err := dest.Commit(); err != nil {
			return err
		}

		return nil
	},
}
<commit_msg>deprecate skopeo layers<commit_after>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/containers/image/directory"
	"github.com/containers/image/image"
	"github.com/containers/image/manifest"
	"github.com/containers/image/types"
	"github.com/urfave/cli"
)

var layersCmd = cli.Command{
	Name:      "layers",
	Usage:     "Get layers of IMAGE-NAME",
	ArgsUsage: "IMAGE-NAME [LAYER...]",
	Action: func(c *cli.Context) error {
		fmt.Fprintln(os.Stderr, `DEPRECATED: skopeo layers is deprecated in favor of skopeo copy`)
		if c.NArg() == 0 {
			return errors.New("Usage: layers imageReference [layer...]")
		}
		rawSource, err := parseImageSource(c, c.Args()[0], []string{
			// TODO: skopeo layers only supports these now
			// eventually we'll remove this command altogether...
			manifest.DockerV2Schema1SignedMediaType,
			manifest.DockerV2Schema1MediaType,
		})
		if err != nil {
			return err
		}
		src, err := image.FromSource(rawSource)
		if err != nil {
			rawSource.Close()
			return err
		}
		defer src.Close()

		blobDigests := c.Args().Tail()
		if len(blobDigests) == 0 {
			layers := src.LayerInfos()
			seenLayers := map[string]struct{}{}
			for _, info := range layers {
				if _, ok := seenLayers[info.Digest]; !ok {
					blobDigests = append(blobDigests, info.Digest)
					seenLayers[info.Digest] = struct{}{}
				}
			}
			configInfo := src.ConfigInfo()
			if configInfo.Digest != "" {
				blobDigests = append(blobDigests, configInfo.Digest)
			}
		}

		tmpDir, err := ioutil.TempDir(".", "layers-")
		if err != nil {
			return err
		}
		tmpDirRef, err := directory.NewReference(tmpDir)
		if err != nil {
			return err
		}
		dest, err := tmpDirRef.NewImageDestination(nil)
		if err != nil {
			return err
		}
		defer dest.Close()

		for _, digest := range blobDigests {
			if !strings.HasPrefix(digest, "sha256:") {
				digest = "sha256:" + digest
			}
			r, blobSize, err := rawSource.GetBlob(digest)
			if err != nil {
				return err
			}
			if _, err := dest.PutBlob(r, types.BlobInfo{Digest: digest, Size: blobSize}); err != nil {
				r.Close()
				return err
			}
			r.Close()
		}

		manifest, _, err := src.Manifest()
		if err != nil {
			return err
		}
		if err := dest.PutManifest(manifest); err != nil {
			return err
		}

		if err := dest.Commit(); err != nil {
			return err
		}

		return nil
	},
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"net/http"

	"github.com/prometheus/client_golang/prometheus/promhttp"
	log "github.com/sirupsen/logrus"
	"github.com/superfly/wormhole"
	"github.com/superfly/wormhole/config"
)

func main() {
	serverMode := flag.Bool("server", false, "Run the wormhole in server mode.")
	versionFlag := flag.Bool("version", false, "Display wormhole version.")
	helpFlag := flag.Bool("help", false, "Show help")
	flag.BoolVar(helpFlag, "h", false, "Show help")
	flag.Parse()

	if *helpFlag {
		fmt.Printf(
			`Wormhole v%s

Usage
=====

  - First, set the required environment variables. (details below)
  - Second, choose a mode to run. (more details below)

    (supervisor)
    wormhole path/to/executable

    (sidecar)
    wormhole

Environment Variables
=====================

  (required)
    FLY_TOKEN: Token for your backend, found on your fly.io backend's page

  (optional)
    FLY_LOCAL_ENDPOINT: Local server to tunnel for. (defaults to "127.0.0.1:5000")
      or
    FLY_PORT: Local port to tunnel. (defaults to 5000)

    FLY_REMOTE_ENDPOINT: Wormhole server instance. Defaults to Fly.io's servers.
    FLY_RELEASE_ID_VAR: ENV var with current released version of your web server (inferred from git if available)
    FLY_RELEASE_DESC_VAR: ENV name with commit message of the current released version of your web server (inferred from git if available)

Modes
=====

  Supervisor (preferred)
  ----------

    Wormhole will start your application (the specified executable) and supervise the process.

    Examples:

      export FLY_TOKEN=x # Always set this.

      # Rails, setting a port
      export FLY_PORT=3000 # Defaults to: 5000
      wormhole bundle exec rails server -p 3000

      # Go, setting a full endpoint
      export FLY_ENDPOINT=127.0.0.1:3000 # Defaults to: 127.0.0.1:5000
      wormhole path/to/app

  Sidecar
  -------

    Wormhole assumes your server is already running.
    You need to set the proper environment variables.

    Examples:

      export FLY_TOKEN=x # Always set this.

      # Setting a port
      export FLY_PORT=3000 # Defaults to: 5000
      wormhole

      # Setting a full endpoint
      export FLY_ENDPOINT=127.0.0.1:3000 # Defaults to: 127.0.0.1:5000
      wormhole

Other commands
==============

  -v, --version: Prints the version for wormhole.
  -h, --help:    Prints this help information.
      --server:  Starts wormhole in server mode. You probably don't want this.

`, config.Version())
		return
	}

	if *versionFlag {
		fmt.Printf("wormhole %s\n", config.Version())
		return
	}

	if *serverMode {
		config, err := config.NewServerConfig()
		if err != nil {
			log.Fatalf("config error: %s", err.Error())
		}

		// Expose the registered metrics via HTTP.
		go func() {
			http.Handle("/metrics", promhttp.Handler())
			config.Logger.Fatal(http.ListenAndServe(":"+config.MetricsAPIPort, nil))
		}()

		wormhole.StartRemote(config)
	} else {
		config, err := config.NewClientConfig()
		if err != nil {
			log.Fatalf("config error: %s", err.Error())
		}
		wormhole.StartLocal(config)
	}
}
<commit_msg>Fix errant FLY_ENDPOINT references<commit_after>package main

import (
	"flag"
	"fmt"
	"net/http"

	"github.com/prometheus/client_golang/prometheus/promhttp"
	log "github.com/sirupsen/logrus"
	"github.com/superfly/wormhole"
	"github.com/superfly/wormhole/config"
)

func main() {
	serverMode := flag.Bool("server", false, "Run the wormhole in server mode.")
	versionFlag := flag.Bool("version", false, "Display wormhole version.")
	helpFlag := flag.Bool("help", false, "Show help")
	flag.BoolVar(helpFlag, "h", false, "Show help")
	flag.Parse()

	if *helpFlag {
		fmt.Printf(
			`Wormhole v%s

Usage
=====

  - First, set the required environment variables. (details below)
  - Second, choose a mode to run. (more details below)

    (supervisor)
    wormhole path/to/executable

    (sidecar)
    wormhole

Environment Variables
=====================

  (required)
    FLY_TOKEN: Token for your backend, found on your fly.io backend's page

  (optional)
    FLY_LOCAL_ENDPOINT: Local server to tunnel for. (defaults to "127.0.0.1:5000")
      or
    FLY_PORT: Local port to tunnel. (defaults to 5000)

    FLY_REMOTE_ENDPOINT: Wormhole server instance. Defaults to Fly.io's servers.
    FLY_RELEASE_ID_VAR: ENV var with current released version of your web server (inferred from git if available)
    FLY_RELEASE_DESC_VAR: ENV name with commit message of the current released version of your web server (inferred from git if available)

Modes
=====

  Supervisor (preferred)
  ----------

    Wormhole will start your application (the specified executable) and supervise the process.

    Examples:

      export FLY_TOKEN=x # Always set this.

      # Rails, setting a port
      export FLY_PORT=3000 # Defaults to: 5000
      wormhole bundle exec rails server -p 3000

      # Go, setting a full endpoint
      export FLY_LOCAL_ENDPOINT=127.0.0.1:3000 # Defaults to: 127.0.0.1:5000
      wormhole path/to/app

  Sidecar
  -------

    Wormhole assumes your server is already running.
    You need to set the proper environment variables.

    Examples:

      export FLY_TOKEN=x # Always set this.

      # Setting a port
      export FLY_PORT=3000 # Defaults to: 5000
      wormhole

      # Setting a full endpoint
      export FLY_LOCAL_ENDPOINT=127.0.0.1:3000 # Defaults to: 127.0.0.1:5000
      wormhole

Other commands
==============

  -v, --version: Prints the version for wormhole.
  -h, --help:    Prints this help information.
      --server:  Starts wormhole in server mode. You probably don't want this.

`, config.Version())
		return
	}

	if *versionFlag {
		fmt.Printf("wormhole %s\n", config.Version())
		return
	}

	if *serverMode {
		config, err := config.NewServerConfig()
		if err != nil {
			log.Fatalf("config error: %s", err.Error())
		}

		// Expose the registered metrics via HTTP.
		go func() {
			http.Handle("/metrics", promhttp.Handler())
			config.Logger.Fatal(http.ListenAndServe(":"+config.MetricsAPIPort, nil))
		}()

		wormhole.StartRemote(config)
	} else {
		config, err := config.NewClientConfig()
		if err != nil {
			log.Fatalf("config error: %s", err.Error())
		}
		wormhole.StartLocal(config)
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc. 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 metadata provides access to Google Compute Engine (GCE)
// metadata and API service accounts.
//
// This package is a wrapper around the GCE metadata service,
// as documented at https://developers.google.com/compute/docs/metadata.
package metadata // import "google.golang.org/cloud/compute/metadata"

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"

	"google.golang.org/cloud/internal"
)

// metadataIP is the documented metadata server IP address.
const metadataIP = "169.254.169.254"

type cachedValue struct {
	k    string
	trim bool
	mu   sync.Mutex
	v    string
}

var (
	projID  = &cachedValue{k: "project/project-id", trim: true}
	projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
	instID  = &cachedValue{k: "instance/id", trim: true}
)

var (
	metaClient = &http.Client{
		Transport: &internal.Transport{
			Base: &http.Transport{
				Dial: (&net.Dialer{
					Timeout:   2 * time.Second,
					KeepAlive: 30 * time.Second,
				}).Dial,
				ResponseHeaderTimeout: 2 * time.Second,
			},
		},
	}
	subscribeClient = &http.Client{
		Transport: &internal.Transport{
			Base: &http.Transport{
				Dial: (&net.Dialer{
					Timeout:   2 * time.Millisecond,
					KeepAlive: 30 * time.Second,
				}).Dial,
			},
		},
	}
)

// NotDefinedError is returned when requested metadata is not defined.
//
// The underlying string is the suffix after "/computeMetadata/v1/".
//
// This error is not returned if the value is defined to be the empty
// string.
type NotDefinedError string

func (suffix NotDefinedError) Error() string {
	return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
}

// Get returns a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// If the GCE_METADATA_HOST environment variable is not defined, a default of
// 169.254.169.254 will be used instead.
//
// If the requested metadata is not defined, the returned error will
// be of type NotDefinedError.
func Get(suffix string) (string, error) {
	val, _, err := getETag(metaClient, suffix)
	return val, err
}

// getETag returns a value from the metadata service as well as the associated
// ETag using the provided client. This func is otherwise equivalent to Get.
func getETag(client *http.Client, suffix string) (value, etag string, err error) {
	// Using a fixed IP makes it very difficult to spoof the metadata service in
	// a container, which is an important use-case for local testing of cloud
	// deployments. To enable spoofing of the metadata service, the environment
	// variable GCE_METADATA_HOST is first inspected to decide where metadata
	// requests shall go.
	host := os.Getenv("GCE_METADATA_HOST")
	if host == "" {
		// Using 169.254.169.254 instead of "metadata" here because Go
		// binaries built with the "netgo" tag and without cgo won't
		// know the search suffix for "metadata" is
		// ".google.internal", and this IP address is documented as
		// being stable anyway.
		host = metadataIP
	}
	url := "http://" + host + "/computeMetadata/v1/" + suffix
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Metadata-Flavor", "Google")
	res, err := client.Do(req)
	if err != nil {
		return "", "", err
	}
	defer res.Body.Close()
	if res.StatusCode == http.StatusNotFound {
		return "", "", NotDefinedError(suffix)
	}
	if res.StatusCode != 200 {
		return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
	}
	all, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", "", err
	}
	return string(all), res.Header.Get("Etag"), nil
}

func getTrimmed(suffix string) (s string, err error) {
	s, err = Get(suffix)
	s = strings.TrimSpace(s)
	return
}

func (c *cachedValue) get() (v string, err error) {
	defer c.mu.Unlock()
	c.mu.Lock()
	if c.v != "" {
		return c.v, nil
	}
	if c.trim {
		v, err = getTrimmed(c.k)
	} else {
		v, err = Get(c.k)
	}
	if err == nil {
		c.v = v
	}
	return
}

var onGCE struct {
	sync.Mutex
	set bool
	v   bool
}

// OnGCE reports whether this process is running on Google Compute Engine.
func OnGCE() bool {
	defer onGCE.Unlock()
	onGCE.Lock()
	if onGCE.set {
		return onGCE.v
	}
	onGCE.set = true
	onGCE.v = testOnGCE()
	return onGCE.v
}

func testOnGCE() bool {
	cancel := make(chan struct{})
	defer close(cancel)

	resc := make(chan bool, 2)

	// Try two strategies in parallel.
	// See https://github.com/GoogleCloudPlatform/gcloud-golang/issues/194
	go func() {
		req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
		req.Cancel = cancel
		res, err := metaClient.Do(req)
		if err != nil {
			resc <- false
			return
		}
		defer res.Body.Close()
		resc <- res.Header.Get("Metadata-Flavor") == "Google"
	}()

	go func() {
		addrs, err := net.LookupHost("metadata.google.internal")
		if err != nil || len(addrs) == 0 {
			resc <- false
			return
		}
		resc <- strsContains(addrs, metadataIP)
	}()

	return <-resc
}

// Subscribe subscribes to a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
// The suffix may contain query parameters.
//
// Subscribe calls fn with the latest metadata value indicated by the provided
// suffix. If the metadata value is deleted, fn is called with the empty string
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
// is deleted. Subscribe returns the error value returned from the last call to
// fn, which may be nil when ok == false.
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
	const failedSubscribeSleep = time.Second * 5

	// First check to see if the metadata value exists at all.
	val, lastETag, err := getETag(subscribeClient, suffix)
	if err != nil {
		return err
	}

	if err := fn(val, true); err != nil {
		return err
	}

	ok := true
	if strings.ContainsRune(suffix, '?') {
		suffix += "&wait_for_change=true&last_etag="
	} else {
		suffix += "?wait_for_change=true&last_etag="
	}
	for {
		val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag))
		if err != nil {
			if _, deleted := err.(NotDefinedError); !deleted {
				time.Sleep(failedSubscribeSleep)
				continue // Retry on other errors.
			}
			ok = false
		}
		lastETag = etag

		if err := fn(val, ok); err != nil || !ok {
			return err
		}
	}
}

// ProjectID returns the current instance's project ID string.
func ProjectID() (string, error) { return projID.get() }

// NumericProjectID returns the current instance's numeric project ID.
func NumericProjectID() (string, error) { return projNum.get() }

// InternalIP returns the instance's primary internal IP address.
func InternalIP() (string, error) {
	return getTrimmed("instance/network-interfaces/0/ip")
}

// ExternalIP returns the instance's primary external (public) IP address.
func ExternalIP() (string, error) {
	return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}

// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func Hostname() (string, error) {
	return getTrimmed("instance/hostname")
}

// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func InstanceTags() ([]string, error) {
	var s []string
	j, err := Get("instance/tags")
	if err != nil {
		return nil, err
	}
	if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
		return nil, err
	}
	return s, nil
}

// InstanceID returns the current VM's numeric instance ID.
func InstanceID() (string, error) {
	return instID.get()
}

// InstanceName returns the current VM's instance ID string.
func InstanceName() (string, error) {
	host, err := Hostname()
	if err != nil {
		return "", err
	}
	return strings.Split(host, ".")[0], nil
}

// Zone returns the current VM's zone, such as "us-central1-b".
func Zone() (string, error) {
	zone, err := getTrimmed("instance/zone")
	// zone is of the form "projects/<projNum>/zones/<zoneName>".
	if err != nil {
		return "", err
	}
	return zone[strings.LastIndex(zone, "/")+1:], nil
}

// InstanceAttributes returns the list of user-defined attributes,
// assigned when initially creating a GCE VM instance. The value of an
// attribute can be obtained with InstanceAttributeValue.
func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }

// ProjectAttributes returns the list of user-defined attributes
// applying to the project as a whole, not just this VM.  The value of
// an attribute can be obtained with ProjectAttributeValue.
func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }

func lines(suffix string) ([]string, error) {
	j, err := Get(suffix)
	if err != nil {
		return nil, err
	}
	s := strings.Split(strings.TrimSpace(j), "\n")
	for i := range s {
		s[i] = strings.TrimSpace(s[i])
	}
	return s, nil
}

// InstanceAttributeValue returns the value of the provided VM
// instance attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// InstanceAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func InstanceAttributeValue(attr string) (string, error) {
	return Get("instance/attributes/" + attr)
}

// ProjectAttributeValue returns the value of the provided
// project attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// ProjectAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func ProjectAttributeValue(attr string) (string, error) {
	return Get("project/attributes/" + attr)
}

// Scopes returns the service account scopes for the given account.
// The account may be empty or the string "default" to use the instance's
// main account.
func Scopes(serviceAccount string) ([]string, error) {
	if serviceAccount == "" {
		serviceAccount = "default"
	}
	return lines("instance/service-accounts/" + serviceAccount + "/scopes")
}

func strsContains(ss []string, s string) bool {
	for _, v := range ss {
		if v == s {
			return true
		}
	}
	return false
}
<commit_msg>compute/metadata: fix timeout, broken in previous commit<commit_after>// Copyright 2014 Google Inc. 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 metadata provides access to Google Compute Engine (GCE)
// metadata and API service accounts.
//
// This package is a wrapper around the GCE metadata service,
// as documented at https://developers.google.com/compute/docs/metadata.
package metadata // import "google.golang.org/cloud/compute/metadata"

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"

	"google.golang.org/cloud/internal"
)

// metadataIP is the documented metadata server IP address.
const metadataIP = "169.254.169.254"

type cachedValue struct {
	k    string
	trim bool
	mu   sync.Mutex
	v    string
}

var (
	projID  = &cachedValue{k: "project/project-id", trim: true}
	projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
	instID  = &cachedValue{k: "instance/id", trim: true}
)

var (
	metaClient = &http.Client{
		Transport: &internal.Transport{
			Base: &http.Transport{
				Dial: (&net.Dialer{
					Timeout:   2 * time.Second,
					KeepAlive: 30 * time.Second,
				}).Dial,
				ResponseHeaderTimeout: 2 * time.Second,
			},
		},
	}
	subscribeClient = &http.Client{
		Transport: &internal.Transport{
			Base: &http.Transport{
				Dial: (&net.Dialer{
					Timeout:   2 * time.Second,
					KeepAlive: 30 * time.Second,
				}).Dial,
			},
		},
	}
)

// NotDefinedError is returned when requested metadata is not defined.
//
// The underlying string is the suffix after "/computeMetadata/v1/".
//
// This error is not returned if the value is defined to be the empty
// string.
type NotDefinedError string

func (suffix NotDefinedError) Error() string {
	return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
}

// Get returns a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
//
// If the GCE_METADATA_HOST environment variable is not defined, a default of
// 169.254.169.254 will be used instead.
//
// If the requested metadata is not defined, the returned error will
// be of type NotDefinedError.
func Get(suffix string) (string, error) {
	val, _, err := getETag(metaClient, suffix)
	return val, err
}

// getETag returns a value from the metadata service as well as the associated
// ETag using the provided client. This func is otherwise equivalent to Get.
func getETag(client *http.Client, suffix string) (value, etag string, err error) {
	// Using a fixed IP makes it very difficult to spoof the metadata service in
	// a container, which is an important use-case for local testing of cloud
	// deployments. To enable spoofing of the metadata service, the environment
	// variable GCE_METADATA_HOST is first inspected to decide where metadata
	// requests shall go.
	host := os.Getenv("GCE_METADATA_HOST")
	if host == "" {
		// Using 169.254.169.254 instead of "metadata" here because Go
		// binaries built with the "netgo" tag and without cgo won't
		// know the search suffix for "metadata" is
		// ".google.internal", and this IP address is documented as
		// being stable anyway.
		host = metadataIP
	}
	url := "http://" + host + "/computeMetadata/v1/" + suffix
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Metadata-Flavor", "Google")
	res, err := client.Do(req)
	if err != nil {
		return "", "", err
	}
	defer res.Body.Close()
	if res.StatusCode == http.StatusNotFound {
		return "", "", NotDefinedError(suffix)
	}
	if res.StatusCode != 200 {
		return "", "", fmt.Errorf("status code %d trying to fetch %s", res.StatusCode, url)
	}
	all, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", "", err
	}
	return string(all), res.Header.Get("Etag"), nil
}

func getTrimmed(suffix string) (s string, err error) {
	s, err = Get(suffix)
	s = strings.TrimSpace(s)
	return
}

func (c *cachedValue) get() (v string, err error) {
	defer c.mu.Unlock()
	c.mu.Lock()
	if c.v != "" {
		return c.v, nil
	}
	if c.trim {
		v, err = getTrimmed(c.k)
	} else {
		v, err = Get(c.k)
	}
	if err == nil {
		c.v = v
	}
	return
}

var onGCE struct {
	sync.Mutex
	set bool
	v   bool
}

// OnGCE reports whether this process is running on Google Compute Engine.
func OnGCE() bool {
	defer onGCE.Unlock()
	onGCE.Lock()
	if onGCE.set {
		return onGCE.v
	}
	onGCE.set = true
	onGCE.v = testOnGCE()
	return onGCE.v
}

func testOnGCE() bool {
	cancel := make(chan struct{})
	defer close(cancel)

	resc := make(chan bool, 2)

	// Try two strategies in parallel.
	// See https://github.com/GoogleCloudPlatform/gcloud-golang/issues/194
	go func() {
		req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
		req.Cancel = cancel
		res, err := metaClient.Do(req)
		if err != nil {
			resc <- false
			return
		}
		defer res.Body.Close()
		resc <- res.Header.Get("Metadata-Flavor") == "Google"
	}()

	go func() {
		addrs, err := net.LookupHost("metadata.google.internal")
		if err != nil || len(addrs) == 0 {
			resc <- false
			return
		}
		resc <- strsContains(addrs, metadataIP)
	}()

	return <-resc
}

// Subscribe subscribes to a value from the metadata service.
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
// The suffix may contain query parameters.
//
// Subscribe calls fn with the latest metadata value indicated by the provided
// suffix. If the metadata value is deleted, fn is called with the empty string
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
// is deleted. Subscribe returns the error value returned from the last call to
// fn, which may be nil when ok == false.
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
	const failedSubscribeSleep = time.Second * 5

	// First check to see if the metadata value exists at all.
	val, lastETag, err := getETag(subscribeClient, suffix)
	if err != nil {
		return err
	}

	if err := fn(val, true); err != nil {
		return err
	}

	ok := true
	if strings.ContainsRune(suffix, '?') {
		suffix += "&wait_for_change=true&last_etag="
	} else {
		suffix += "?wait_for_change=true&last_etag="
	}
	for {
		val, etag, err := getETag(subscribeClient, suffix+url.QueryEscape(lastETag))
		if err != nil {
			if _, deleted := err.(NotDefinedError); !deleted {
				time.Sleep(failedSubscribeSleep)
				continue // Retry on other errors.
			}
			ok = false
		}
		lastETag = etag

		if err := fn(val, ok); err != nil || !ok {
			return err
		}
	}
}

// ProjectID returns the current instance's project ID string.
func ProjectID() (string, error) { return projID.get() }

// NumericProjectID returns the current instance's numeric project ID.
func NumericProjectID() (string, error) { return projNum.get() }

// InternalIP returns the instance's primary internal IP address.
func InternalIP() (string, error) {
	return getTrimmed("instance/network-interfaces/0/ip")
}

// ExternalIP returns the instance's primary external (public) IP address.
func ExternalIP() (string, error) {
	return getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
}

// Hostname returns the instance's hostname. This will be of the form
// "<instanceID>.c.<projID>.internal".
func Hostname() (string, error) {
	return getTrimmed("instance/hostname")
}

// InstanceTags returns the list of user-defined instance tags,
// assigned when initially creating a GCE instance.
func InstanceTags() ([]string, error) {
	var s []string
	j, err := Get("instance/tags")
	if err != nil {
		return nil, err
	}
	if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
		return nil, err
	}
	return s, nil
}

// InstanceID returns the current VM's numeric instance ID.
func InstanceID() (string, error) {
	return instID.get()
}

// InstanceName returns the current VM's instance ID string.
func InstanceName() (string, error) {
	host, err := Hostname()
	if err != nil {
		return "", err
	}
	return strings.Split(host, ".")[0], nil
}

// Zone returns the current VM's zone, such as "us-central1-b".
func Zone() (string, error) {
	zone, err := getTrimmed("instance/zone")
	// zone is of the form "projects/<projNum>/zones/<zoneName>".
	if err != nil {
		return "", err
	}
	return zone[strings.LastIndex(zone, "/")+1:], nil
}

// InstanceAttributes returns the list of user-defined attributes,
// assigned when initially creating a GCE VM instance. The value of an
// attribute can be obtained with InstanceAttributeValue.
func InstanceAttributes() ([]string, error) { return lines("instance/attributes/") }

// ProjectAttributes returns the list of user-defined attributes
// applying to the project as a whole, not just this VM.  The value of
// an attribute can be obtained with ProjectAttributeValue.
func ProjectAttributes() ([]string, error) { return lines("project/attributes/") }

func lines(suffix string) ([]string, error) {
	j, err := Get(suffix)
	if err != nil {
		return nil, err
	}
	s := strings.Split(strings.TrimSpace(j), "\n")
	for i := range s {
		s[i] = strings.TrimSpace(s[i])
	}
	return s, nil
}

// InstanceAttributeValue returns the value of the provided VM
// instance attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// InstanceAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func InstanceAttributeValue(attr string) (string, error) {
	return Get("instance/attributes/" + attr)
}

// ProjectAttributeValue returns the value of the provided
// project attribute.
//
// If the requested attribute is not defined, the returned error will
// be of type NotDefinedError.
//
// ProjectAttributeValue may return ("", nil) if the attribute was
// defined to be the empty string.
func ProjectAttributeValue(attr string) (string, error) {
	return Get("project/attributes/" + attr)
}

// Scopes returns the service account scopes for the given account.
// The account may be empty or the string "default" to use the instance's
// main account.
func Scopes(serviceAccount string) ([]string, error) {
	if serviceAccount == "" {
		serviceAccount = "default"
	}
	return lines("instance/service-accounts/" + serviceAccount + "/scopes")
}

func strsContains(ss []string, s string) bool {
	for _, v := range ss {
		if v == s {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Knative 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 domains

import (
	"context"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/google/go-cmp/cmp"
	"knative.dev/pkg/apis"
	pkgnet "knative.dev/pkg/network"

	network "knative.dev/networking/pkg"
	"knative.dev/serving/pkg/apis/serving"
	v1 "knative.dev/serving/pkg/apis/serving/v1"
	"knative.dev/serving/pkg/gc"
	"knative.dev/serving/pkg/reconciler/route/config"
)

func testConfig() *config.Config {
	return &config.Config{
		Domain: &config.Domain{
			Domains: map[string]*config.LabelSelector{
				"example.com": {},
				"another-example.com": {
					Selector: map[string]string{"app": "prod"},
				},
			},
		},
		Network: &network.Config{
			DefaultIngressClass: "ingress-class-foo",
			DomainTemplate:      network.DefaultDomainTemplate,
		},
		GC: &gc.Config{
			StaleRevisionLastpinnedDebounce: 1 * time.Minute,
		},
	}
}

func TestDomainNameFromTemplate(t *testing.T) {
	type args struct {
		name string
	}
	tests := []struct {
		name     string
		template string
		args     args
		want     string
		wantErr  bool
		local    bool
	}{{
		name:     "Default",
		template: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		args:     args{name: "test-name"},
		want:     "test-name.default.example.com",
		local:    false,
	}, {
		name:     "Dash",
		template: "{{.Name}}-{{.Namespace}}.{{.Domain}}",
		args:     args{name: "test-name"},
		want:     "test-name-default.example.com",
		local:    false,
	}, {
		name:     "LocalDash",
		template: "{{.Name}}-{{.Namespace}}.{{.Domain}}",
		args:     args{name: "test-name"},
		want:     pkgnet.GetServiceHostname("test-name", "default"),
		local:    true,
	}, {
		name:     "Short",
		template: "{{.Name}}.{{.Domain}}",
		args:     args{name: "test-name"},
		want:     "test-name.example.com",
		local:    false,
	}, {
		name:     "Too Short", // domain must be at least two segments separated by dots.
		template: "{{.Name}}",
		args:     args{name: "test-name"},
		wantErr:  true,
		local:    false,
	}, {
		name:     "Annotations",
		template: `{{.Name}}.{{ index .Annotations "sub"}}.{{.Domain}}`,
		args:     args{name: "test-name"},
		want:     "test-name.mysub.example.com",
		local:    false,
	}, {
		name:     "Labels",
		template: `{{.Name}}.{{ index .Labels "bus"}}.{{.Domain}}`,
		args:     args{name: "test-name"},
		want:     "test-name.mybus.example.com",
		local:    false,
	}, {
		// This cannot get through our validation, but verify we handle errors.
		name:     "BadVarName",
		template: "{{.Name}}.{{.NNNamespace}}.{{.Domain}}",
		args:     args{name: "test-name"},
		wantErr:  true,
		local:    false,
	}, {
		name:     "Invalid domain name",
		template: "{{.Name}}.{{.Namespace}}.{{.Domain}}.Foo",
		args:     args{name: "test-name"},
		wantErr:  true,
		local:    false,
	}}

	meta := metav1.ObjectMeta{
		SelfLink:  "/apis/serving/v1/namespaces/test/Routes/myapp",
		Name:      "myroute",
		Namespace: "default",
		Labels: map[string]string{
			"route": "myapp",
			"bus":   "mybus",
		},
		Annotations: map[string]string{
			"sub": "mysub",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			cfg := testConfig()
			cfg.Network.DomainTemplate = tt.template
			ctx = config.ToContext(ctx, cfg)

			if tt.local {
				meta.Labels[network.VisibilityLabelKey] = serving.VisibilityClusterLocal
			} else {
				delete(meta.Labels, network.VisibilityLabelKey)
			}

			got, err := DomainNameFromTemplate(ctx, meta, tt.args.name)
			if (err != nil) != tt.wantErr {
				t.Errorf("DomainNameFromTemplate() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("DomainNameFromTemplate() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestURL(t *testing.T) {
	tests := []struct {
		name     string
		scheme   string
		domain   string
		Expected apis.URL
	}{{
		name:   "subdomain",
		scheme: HTTPScheme,
		domain: "current.svc.local.com",
		Expected: apis.URL{
			Scheme: "http",
			Host:   "current.svc.local.com",
		},
	}, {
		name:   "default target",
		scheme: HTTPScheme,
		domain: "example.com",
		Expected: apis.URL{
			Scheme: "http",
			Host:   "example.com",
		},
	}}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got, want := *URL(tt.scheme, tt.domain), tt.Expected; !cmp.Equal(want, got) {
				t.Errorf("URL = %v, want: %v", got, want)
			}
		})
	}
}

func TestGetAllDomainsAndTags(t *testing.T) {
	tests := []struct {
		name           string
		domainTemplate string
		tagTemplate    string
		want           map[string]string
		wantErr        bool
	}{{
		name:           "happy case",
		domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "{{.Name}}-{{.Tag}}",
		want: map[string]string{
			"myroute-target-1.default.example.com": "target-1",
			"myroute-target-2.default.example.com": "target-2",
			"myroute.default.example.com":          "",
		},
	}, {
		name:           "another happy case",
		domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "{{.Tag}}-{{.Name}}",
		want: map[string]string{
			"target-1-myroute.default.example.com": "target-1",
			"target-2-myroute.default.example.com": "target-2",
			"myroute.default.example.com":          "",
		},
	}, {
		name:           "or appengine style",
		domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "{{.Tag}}-dot-{{.Name}}",
		want: map[string]string{
			"target-1-dot-myroute.default.example.com": "target-1",
			"target-2-dot-myroute.default.example.com": "target-2",
			"myroute.default.example.com":              "",
		},
	}, {
		name:           "bad template in domain",
		domainTemplate: "{{.NNName}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "{{.Name}}-{{.Tag}}",
		wantErr:        true,
	}, {
		name:           "bad template in template",
		domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "{{.NNName}}-{{.Tag}}",
		wantErr:        true,
	}, {
		name:           "bad domain name",
		domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "Foo.{{.Name}}-{{.Tag}}",
		wantErr:        true,
	}}

	route := &v1.Route{
		ObjectMeta: metav1.ObjectMeta{
			SelfLink:  "/apis/serving/v1/namespaces/test/Routes/myapp",
			Name:      "myroute",
			Namespace: "default",
			Labels: map[string]string{
				"route": "myapp",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			cfg := testConfig()
			cfg.Network.DomainTemplate = tt.domainTemplate
			cfg.Network.TagTemplate = tt.tagTemplate
			ctx = config.ToContext(ctx, cfg)

			// here, a tag-less major domain will have empty string as the input
			got, err := GetAllDomainsAndTags(ctx, route, []string{"", "target-1", "target-2"}, nil /* visibility */)
			if (err != nil) != tt.wantErr {
				t.Errorf("GetAllDomains() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if diff := cmp.Diff(tt.want, got); diff != "" {
				t.Error("GetAllDomains() diff (-want +got):", diff)
			}
		})
	}
}
func TestIsClusterLocal(t *testing.T) {
	tests := []struct {
		name   string
		domain string
		want   bool
	}{
		{
			name:   "domain is public",
			domain: "k8s.io",
			want:   false,
		},
		{
			name:   "domain is cluster local",
			domain: "my-app.cluster.local",
			want:   true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := IsClusterLocal(tt.domain); got != tt.want {
				t.Errorf("IsClusterLocal() = %v, want %v", got, tt.want)
			}
		})
	}
}
<commit_msg>Fix the nits I lgtm'd without thinking (#10036)<commit_after>/*
Copyright 2019 The Knative 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 domains

import (
	"context"
	"testing"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/google/go-cmp/cmp"
	"knative.dev/pkg/apis"
	pkgnet "knative.dev/pkg/network"

	network "knative.dev/networking/pkg"
	"knative.dev/serving/pkg/apis/serving"
	v1 "knative.dev/serving/pkg/apis/serving/v1"
	"knative.dev/serving/pkg/gc"
	"knative.dev/serving/pkg/reconciler/route/config"
)

func testConfig() *config.Config {
	return &config.Config{
		Domain: &config.Domain{
			Domains: map[string]*config.LabelSelector{
				"example.com": {},
				"another-example.com": {
					Selector: map[string]string{"app": "prod"},
				},
			},
		},
		Network: &network.Config{
			DefaultIngressClass: "ingress-class-foo",
			DomainTemplate:      network.DefaultDomainTemplate,
		},
		GC: &gc.Config{
			StaleRevisionLastpinnedDebounce: 1 * time.Minute,
		},
	}
}

func TestDomainNameFromTemplate(t *testing.T) {
	type args struct {
		name string
	}
	tests := []struct {
		name     string
		template string
		args     args
		want     string
		wantErr  bool
		local    bool
	}{{
		name:     "Default",
		template: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		args:     args{name: "test-name"},
		want:     "test-name.default.example.com",
		local:    false,
	}, {
		name:     "Dash",
		template: "{{.Name}}-{{.Namespace}}.{{.Domain}}",
		args:     args{name: "test-name"},
		want:     "test-name-default.example.com",
		local:    false,
	}, {
		name:     "LocalDash",
		template: "{{.Name}}-{{.Namespace}}.{{.Domain}}",
		args:     args{name: "test-name"},
		want:     pkgnet.GetServiceHostname("test-name", "default"),
		local:    true,
	}, {
		name:     "Short",
		template: "{{.Name}}.{{.Domain}}",
		args:     args{name: "test-name"},
		want:     "test-name.example.com",
		local:    false,
	}, {
		name:     "Too Short", // domain must be at least two segments separated by dots.
		template: "{{.Name}}",
		args:     args{name: "test-name"},
		wantErr:  true,
		local:    false,
	}, {
		name:     "Annotations",
		template: `{{.Name}}.{{ index .Annotations "sub"}}.{{.Domain}}`,
		args:     args{name: "test-name"},
		want:     "test-name.mysub.example.com",
		local:    false,
	}, {
		name:     "Labels",
		template: `{{.Name}}.{{ index .Labels "bus"}}.{{.Domain}}`,
		args:     args{name: "test-name"},
		want:     "test-name.mybus.example.com",
		local:    false,
	}, {
		// This cannot get through our validation, but verify we handle errors.
		name:     "BadVarName",
		template: "{{.Name}}.{{.NNNamespace}}.{{.Domain}}",
		args:     args{name: "test-name"},
		wantErr:  true,
		local:    false,
	}, {
		name:     "Invalid domain name",
		template: "{{.Name}}.{{.Namespace}}.{{.Domain}}.Foo",
		args:     args{name: "test-name"},
		wantErr:  true,
		local:    false,
	}}

	meta := metav1.ObjectMeta{
		SelfLink:  "/apis/serving/v1/namespaces/test/Routes/myapp",
		Name:      "myroute",
		Namespace: "default",
		Labels: map[string]string{
			"route": "myapp",
			"bus":   "mybus",
		},
		Annotations: map[string]string{
			"sub": "mysub",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			cfg := testConfig()
			cfg.Network.DomainTemplate = tt.template
			ctx = config.ToContext(ctx, cfg)

			if tt.local {
				meta.Labels[network.VisibilityLabelKey] = serving.VisibilityClusterLocal
			} else {
				delete(meta.Labels, network.VisibilityLabelKey)
			}

			got, err := DomainNameFromTemplate(ctx, meta, tt.args.name)
			if (err != nil) != tt.wantErr {
				t.Errorf("DomainNameFromTemplate() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("DomainNameFromTemplate() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestURL(t *testing.T) {
	tests := []struct {
		name     string
		scheme   string
		domain   string
		Expected apis.URL
	}{{
		name:   "subdomain",
		scheme: HTTPScheme,
		domain: "current.svc.local.com",
		Expected: apis.URL{
			Scheme: "http",
			Host:   "current.svc.local.com",
		},
	}, {
		name:   "default target",
		scheme: HTTPScheme,
		domain: "example.com",
		Expected: apis.URL{
			Scheme: "http",
			Host:   "example.com",
		},
	}}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got, want := *URL(tt.scheme, tt.domain), tt.Expected; !cmp.Equal(want, got) {
				t.Errorf("URL = %v, want: %v", got, want)
			}
		})
	}
}

func TestGetAllDomainsAndTags(t *testing.T) {
	tests := []struct {
		name           string
		domainTemplate string
		tagTemplate    string
		want           map[string]string
		wantErr        bool
	}{{
		name:           "happy case",
		domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "{{.Name}}-{{.Tag}}",
		want: map[string]string{
			"myroute-target-1.default.example.com": "target-1",
			"myroute-target-2.default.example.com": "target-2",
			"myroute.default.example.com":          "",
		},
	}, {
		name:           "another happy case",
		domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "{{.Tag}}-{{.Name}}",
		want: map[string]string{
			"target-1-myroute.default.example.com": "target-1",
			"target-2-myroute.default.example.com": "target-2",
			"myroute.default.example.com":          "",
		},
	}, {
		name:           "or appengine style",
		domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "{{.Tag}}-dot-{{.Name}}",
		want: map[string]string{
			"target-1-dot-myroute.default.example.com": "target-1",
			"target-2-dot-myroute.default.example.com": "target-2",
			"myroute.default.example.com":              "",
		},
	}, {
		name:           "bad template in domain template",
		domainTemplate: "{{.NNName}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "{{.Name}}-{{.Tag}}",
		wantErr:        true,
	}, {
		name:           "bad template in tag template",
		domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "{{.NNName}}-{{.Tag}}",
		wantErr:        true,
	}, {
		name:           "bad domain name",
		domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}",
		tagTemplate:    "Foo.{{.Name}}-{{.Tag}}",
		wantErr:        true,
	}}

	route := &v1.Route{
		ObjectMeta: metav1.ObjectMeta{
			SelfLink:  "/apis/serving/v1/namespaces/test/Routes/myapp",
			Name:      "myroute",
			Namespace: "default",
			Labels: map[string]string{
				"route": "myapp",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			cfg := testConfig()
			cfg.Network.DomainTemplate = tt.domainTemplate
			cfg.Network.TagTemplate = tt.tagTemplate
			ctx = config.ToContext(ctx, cfg)

			// here, a tag-less major domain will have empty string as the input
			got, err := GetAllDomainsAndTags(ctx, route, []string{"", "target-1", "target-2"}, nil /* visibility */)
			if (err != nil) != tt.wantErr {
				t.Errorf("GetAllDomains() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if diff := cmp.Diff(tt.want, got); diff != "" {
				t.Error("GetAllDomains() diff (-want +got):", diff)
			}
		})
	}
}
func TestIsClusterLocal(t *testing.T) {
	tests := []struct {
		name   string
		domain string
		want   bool
	}{
		{
			name:   "domain is public",
			domain: "k8s.io",
			want:   false,
		},
		{
			name:   "domain is cluster local",
			domain: "my-app.cluster.local",
			want:   true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := IsClusterLocal(tt.domain); got != tt.want {
				t.Errorf("IsClusterLocal() = %v, want %v", got, tt.want)
			}
		})
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/Syfaro/telegram-bot-api"
)

// User - one telegram user who interact with bot
type User struct {
	UserID         int       `json:"user_id"`          // telegram UserID
	UserName       string    `json:"user_name"`        // telegram @login
	FirstName      string    `json:"first_name"`       // telegram name
	LastName       string    `json:"last_name"`        // -//-
	AuthCode       string    `json:"auth_code"`        // code for authorize
	AuthCodeRoot   string    `json:"auth_code_root"`   // code for authorize root
	IsAuthorized   bool      `json:"is_authorized"`    // user allow chat with bot
	IsRoot         bool      `json:"is_root"`          // user is root, allow authorize/ban other users, remove commands, stop bot
	PrivateChatID  int       `json:"private_chat_id"`  // last private chat with bot
	Counter        int       `json:"counter"`          // how many commands send
	LastAccessTime time.Time `json:"last_access_time"` // time of last command
}

// Users in chat
type Users struct {
	list                   map[int]*User
	predefinedAllowedUsers map[string]bool
	predefinedRootUsers    map[string]bool
	needSaveDB             bool // non-saved changes in list
}

// Users for save into JSON
type UsersDB struct {
	Users    []User    `json:"users"`
	DateTime time.Time `json:"date_time"`
}

// clear old users after 20 minutes after login
const SECONDS_FOR_OLD_USERS_BEFORE_VACUUM = 1200

// NewUsers - create Users object
func NewUsers(appConfig Config) Users {
	users := Users{
		list: map[int]*User{},
		predefinedAllowedUsers: map[string]bool{},
		predefinedRootUsers:    map[string]bool{},
		needSaveDB:             true,
	}

	if appConfig.persistentUsers {
		users.LoadFromDB(appConfig.usersDB)
	}

	for _, name := range appConfig.predefinedAllowedUsers {
		users.predefinedAllowedUsers[name] = true
	}
	for _, name := range appConfig.predefinedRootUsers {
		users.predefinedAllowedUsers[name] = true
		users.predefinedRootUsers[name] = true
	}
	return users
}

// AddNew - add new user if not exists
func (users *Users) AddNew(tgbotMessage tgbotapi.Message) {
	privateChatID := 0
	if !tgbotMessage.IsGroup() {
		privateChatID = tgbotMessage.Chat.ID
	}

	if _, ok := users.list[tgbotMessage.From.ID]; ok && privateChatID > 0 && privateChatID != users.list[tgbotMessage.From.ID].PrivateChatID {
		users.list[tgbotMessage.From.ID].PrivateChatID = privateChatID
		users.needSaveDB = true
	} else if !ok {
		users.list[tgbotMessage.From.ID] = &User{
			UserID:        tgbotMessage.From.ID,
			UserName:      tgbotMessage.From.UserName,
			FirstName:     tgbotMessage.From.FirstName,
			LastName:      tgbotMessage.From.LastName,
			IsAuthorized:  users.predefinedAllowedUsers[tgbotMessage.From.UserName],
			IsRoot:        users.predefinedRootUsers[tgbotMessage.From.UserName],
			PrivateChatID: privateChatID,
		}
		users.needSaveDB = true
	}

	// collect stat
	users.list[tgbotMessage.From.ID].LastAccessTime = time.Now()
	if users.list[tgbotMessage.From.ID].IsAuthorized {
		users.list[tgbotMessage.From.ID].Counter++
	}
}

// DoLogin - generate secret code
func (users *Users) DoLogin(userID int, forRoot bool) string {
	code := getRandomCode()
	if forRoot {
		users.list[userID].IsRoot = false
		users.list[userID].AuthCodeRoot = code
	} else {
		users.list[userID].IsAuthorized = false
		users.list[userID].AuthCode = code
	}
	users.needSaveDB = true

	return code
}

// SetAuthorized - set user authorized or authorized as root
func (users *Users) SetAuthorized(userID int, forRoot bool) {
	users.list[userID].IsAuthorized = true
	users.list[userID].AuthCode = ""
	if forRoot {
		users.list[userID].IsRoot = true
		users.list[userID].AuthCodeRoot = ""
	}
	users.needSaveDB = true
}

// IsValidCode - check secret code for user
func (users Users) IsValidCode(userID int, code string, forRoot bool) bool {
	var result bool
	if forRoot {
		result = code != "" && code == users.list[userID].AuthCodeRoot
	} else {
		result = code != "" && code == users.list[userID].AuthCode
	}
	return result
}

// IsAuthorized - check user is authorized
func (users Users) IsAuthorized(userID int) bool {
	isAuthorized := false
	if _, ok := users.list[userID]; ok && users.list[userID].IsAuthorized {
		isAuthorized = true
	}

	return isAuthorized
}

// IsRoot - check user is root
func (users Users) IsRoot(userID int) bool {
	isRoot := false
	if _, ok := users.list[userID]; ok && users.list[userID].IsRoot {
		isRoot = true
	}

	return isRoot
}

// BroadcastForRoots - send message to all root users
func (users Users) BroadcastForRoots(messageSignal chan<- BotMessage, message string, excludeID int) {
	for userID, user := range users.list {
		if user.IsRoot && user.PrivateChatID > 0 && (excludeID == 0 || excludeID != userID) {
			sendMessage(messageSignal, user.PrivateChatID, message)
		}
	}
}

// String - format user name
func (users Users) String(userID int) string {
	result := fmt.Sprintf("%s %s", users.list[userID].FirstName, users.list[userID].LastName)
	if users.list[userID].UserName != "" {
		result += fmt.Sprintf(" (@%s)", users.list[userID].UserName)
	}
	return result
}

// StringVerbose - format user name with all fields
func (users Users) StringVerbose(userID int) string {
	user := users.list[userID]
	result := fmt.Sprintf("%s: id: %d, auth: %v, root: %v, count: %d, last: %v",
		users.String(userID),
		userID,
		user.IsAuthorized,
		user.IsRoot,
		user.Counter,
		user.LastAccessTime.Format("2006-01-02 15:04:05"),
	)
	return result
}

// ClearOldUsers - clear old users without login
func (users *Users) ClearOldUsers() {
	for id, user := range users.list {
		if !user.IsAuthorized && !user.IsRoot && user.Counter == 0 &&
			time.Now().Sub(user.LastAccessTime).Seconds() > SECONDS_FOR_OLD_USERS_BEFORE_VACUUM {
			log.Printf("Vacuum: %d, %s", id, users.String(id))
			delete(users.list, id)
			users.needSaveDB = true
		}
	}
}

// GetUserIDByName - find user by login
func (users Users) GetUserIDByName(userName string) int {
	userID := 0
	for id, user := range users.list {
		if userName == user.UserName {
			userID = id
			break
		}
	}

	return userID
}

// BanUser - ban user by ID
func (users *Users) BanUser(userID int) bool {

	if _, ok := users.list[userID]; ok {
		users.list[userID].IsAuthorized = false
		users.list[userID].IsRoot = false
		if users.list[userID].UserName != "" {
			delete(users.predefinedAllowedUsers, users.list[userID].UserName)
			delete(users.predefinedRootUsers, users.list[userID].UserName)
		}
		users.needSaveDB = true
		return true
	}

	return false
}

// Search - search users
func (users Users) Search(query string) (result []int) {
	queryUserID, _ := strconv.Atoi(query)
	query = strings.ToLower(query)
	queryAsLogin := cleanUserName(query)

	for userID, user := range users.list {
		if queryUserID == userID ||
			strings.Contains(strings.ToLower(user.UserName), queryAsLogin) ||
			strings.Contains(strings.ToLower(user.FirstName), query) ||
			strings.Contains(strings.ToLower(user.LastName), query) {
			result = append(result, userID)
		}
	}

	return result
}

// FindByIDOrUserName - find users or by ID or by @name
func (users Users) FindByIDOrUserName(userName string) int {
	userID, err := strconv.Atoi(userName)
	if err == nil {
		if _, ok := users.list[userID]; !ok {
			userID = 0
		}
	} else {
		userName = cleanUserName(userName)
		userID = users.GetUserIDByName(userName)
	}

	return userID
}

// SendMessageToPrivate - send message to user to private chat
func (users Users) SendMessageToPrivate(messageSignal chan<- BotMessage, userID int, message string) bool {
	if user, ok := users.list[userID]; ok && user.PrivateChatID > 0 {
		sendMessage(messageSignal, user.PrivateChatID, message)
		return true
	}
	return false
}

// LoadFromDB - load users list from json file
func (users *Users) LoadFromDB(usersDBFile string) {
	usersList := UsersDB{}

	fileNamePath := getDBFilePath(usersDBFile, false)
	usersJSON, err := ioutil.ReadFile(fileNamePath)
	if err == nil {
		if err = json.Unmarshal(usersJSON, &usersList); err == nil {
			for _, user := range usersList.Users {
				users.list[user.UserID] = &user
			}
		}
	}
	if err == nil {
		log.Printf("Loaded usersDB json from: %s", fileNamePath)
	} else {
		log.Printf("Load usersDB (%s) error: %s", fileNamePath, err)
	}

	users.needSaveDB = false
}

// SaveToDB - save users list to json file
func (users *Users) SaveToDB(usersDBFile string) {
	if users.needSaveDB {
		usersList := UsersDB{
			Users:    []User{},
			DateTime: time.Now(),
		}
		for _, user := range users.list {
			usersList.Users = append(usersList.Users, *user)
		}

		fileNamePath := getDBFilePath(usersDBFile, true)
		json, err := json.MarshalIndent(usersList, "", "  ")
		if err == nil {
			err = ioutil.WriteFile(fileNamePath, json, 0644)
		}

		if err == nil {
			log.Printf("Saved usersDB json to: %s", fileNamePath)
		} else {
			log.Printf("Save usersDB (%s) error: %s", fileNamePath, err)
		}

		users.needSaveDB = false
	}
}
<commit_msg>Changes for golint<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/Syfaro/telegram-bot-api"
)

// User - one telegram user who interact with bot
type User struct {
	UserID         int       `json:"user_id"`          // telegram UserID
	UserName       string    `json:"user_name"`        // telegram @login
	FirstName      string    `json:"first_name"`       // telegram name
	LastName       string    `json:"last_name"`        // -//-
	AuthCode       string    `json:"auth_code"`        // code for authorize
	AuthCodeRoot   string    `json:"auth_code_root"`   // code for authorize root
	IsAuthorized   bool      `json:"is_authorized"`    // user allow chat with bot
	IsRoot         bool      `json:"is_root"`          // user is root, allow authorize/ban other users, remove commands, stop bot
	PrivateChatID  int       `json:"private_chat_id"`  // last private chat with bot
	Counter        int       `json:"counter"`          // how many commands send
	LastAccessTime time.Time `json:"last_access_time"` // time of last command
}

// Users in chat
type Users struct {
	list                   map[int]*User
	predefinedAllowedUsers map[string]bool
	predefinedRootUsers    map[string]bool
	needSaveDB             bool // non-saved changes in list
}

// UsersDB -  save list of Users into JSON
type UsersDB struct {
	Users    []User    `json:"users"`
	DateTime time.Time `json:"date_time"`
}

// clear old users after 20 minutes after login
const SECONDS_FOR_OLD_USERS_BEFORE_VACUUM = 1200

// NewUsers - create Users object
func NewUsers(appConfig Config) Users {
	users := Users{
		list: map[int]*User{},
		predefinedAllowedUsers: map[string]bool{},
		predefinedRootUsers:    map[string]bool{},
		needSaveDB:             true,
	}

	if appConfig.persistentUsers {
		users.LoadFromDB(appConfig.usersDB)
	}

	for _, name := range appConfig.predefinedAllowedUsers {
		users.predefinedAllowedUsers[name] = true
	}
	for _, name := range appConfig.predefinedRootUsers {
		users.predefinedAllowedUsers[name] = true
		users.predefinedRootUsers[name] = true
	}
	return users
}

// AddNew - add new user if not exists
func (users *Users) AddNew(tgbotMessage tgbotapi.Message) {
	privateChatID := 0
	if !tgbotMessage.IsGroup() {
		privateChatID = tgbotMessage.Chat.ID
	}

	if _, ok := users.list[tgbotMessage.From.ID]; ok && privateChatID > 0 && privateChatID != users.list[tgbotMessage.From.ID].PrivateChatID {
		users.list[tgbotMessage.From.ID].PrivateChatID = privateChatID
		users.needSaveDB = true
	} else if !ok {
		users.list[tgbotMessage.From.ID] = &User{
			UserID:        tgbotMessage.From.ID,
			UserName:      tgbotMessage.From.UserName,
			FirstName:     tgbotMessage.From.FirstName,
			LastName:      tgbotMessage.From.LastName,
			IsAuthorized:  users.predefinedAllowedUsers[tgbotMessage.From.UserName],
			IsRoot:        users.predefinedRootUsers[tgbotMessage.From.UserName],
			PrivateChatID: privateChatID,
		}
		users.needSaveDB = true
	}

	// collect stat
	users.list[tgbotMessage.From.ID].LastAccessTime = time.Now()
	if users.list[tgbotMessage.From.ID].IsAuthorized {
		users.list[tgbotMessage.From.ID].Counter++
	}
}

// DoLogin - generate secret code
func (users *Users) DoLogin(userID int, forRoot bool) string {
	code := getRandomCode()
	if forRoot {
		users.list[userID].IsRoot = false
		users.list[userID].AuthCodeRoot = code
	} else {
		users.list[userID].IsAuthorized = false
		users.list[userID].AuthCode = code
	}
	users.needSaveDB = true

	return code
}

// SetAuthorized - set user authorized or authorized as root
func (users *Users) SetAuthorized(userID int, forRoot bool) {
	users.list[userID].IsAuthorized = true
	users.list[userID].AuthCode = ""
	if forRoot {
		users.list[userID].IsRoot = true
		users.list[userID].AuthCodeRoot = ""
	}
	users.needSaveDB = true
}

// IsValidCode - check secret code for user
func (users Users) IsValidCode(userID int, code string, forRoot bool) bool {
	var result bool
	if forRoot {
		result = code != "" && code == users.list[userID].AuthCodeRoot
	} else {
		result = code != "" && code == users.list[userID].AuthCode
	}
	return result
}

// IsAuthorized - check user is authorized
func (users Users) IsAuthorized(userID int) bool {
	isAuthorized := false
	if _, ok := users.list[userID]; ok && users.list[userID].IsAuthorized {
		isAuthorized = true
	}

	return isAuthorized
}

// IsRoot - check user is root
func (users Users) IsRoot(userID int) bool {
	isRoot := false
	if _, ok := users.list[userID]; ok && users.list[userID].IsRoot {
		isRoot = true
	}

	return isRoot
}

// BroadcastForRoots - send message to all root users
func (users Users) BroadcastForRoots(messageSignal chan<- BotMessage, message string, excludeID int) {
	for userID, user := range users.list {
		if user.IsRoot && user.PrivateChatID > 0 && (excludeID == 0 || excludeID != userID) {
			sendMessage(messageSignal, user.PrivateChatID, message)
		}
	}
}

// String - format user name
func (users Users) String(userID int) string {
	result := fmt.Sprintf("%s %s", users.list[userID].FirstName, users.list[userID].LastName)
	if users.list[userID].UserName != "" {
		result += fmt.Sprintf(" (@%s)", users.list[userID].UserName)
	}
	return result
}

// StringVerbose - format user name with all fields
func (users Users) StringVerbose(userID int) string {
	user := users.list[userID]
	result := fmt.Sprintf("%s: id: %d, auth: %v, root: %v, count: %d, last: %v",
		users.String(userID),
		userID,
		user.IsAuthorized,
		user.IsRoot,
		user.Counter,
		user.LastAccessTime.Format("2006-01-02 15:04:05"),
	)
	return result
}

// ClearOldUsers - clear old users without login
func (users *Users) ClearOldUsers() {
	for id, user := range users.list {
		if !user.IsAuthorized && !user.IsRoot && user.Counter == 0 &&
			time.Now().Sub(user.LastAccessTime).Seconds() > SECONDS_FOR_OLD_USERS_BEFORE_VACUUM {
			log.Printf("Vacuum: %d, %s", id, users.String(id))
			delete(users.list, id)
			users.needSaveDB = true
		}
	}
}

// GetUserIDByName - find user by login
func (users Users) GetUserIDByName(userName string) int {
	userID := 0
	for id, user := range users.list {
		if userName == user.UserName {
			userID = id
			break
		}
	}

	return userID
}

// BanUser - ban user by ID
func (users *Users) BanUser(userID int) bool {

	if _, ok := users.list[userID]; ok {
		users.list[userID].IsAuthorized = false
		users.list[userID].IsRoot = false
		if users.list[userID].UserName != "" {
			delete(users.predefinedAllowedUsers, users.list[userID].UserName)
			delete(users.predefinedRootUsers, users.list[userID].UserName)
		}
		users.needSaveDB = true
		return true
	}

	return false
}

// Search - search users
func (users Users) Search(query string) (result []int) {
	queryUserID, _ := strconv.Atoi(query)
	query = strings.ToLower(query)
	queryAsLogin := cleanUserName(query)

	for userID, user := range users.list {
		if queryUserID == userID ||
			strings.Contains(strings.ToLower(user.UserName), queryAsLogin) ||
			strings.Contains(strings.ToLower(user.FirstName), query) ||
			strings.Contains(strings.ToLower(user.LastName), query) {
			result = append(result, userID)
		}
	}

	return result
}

// FindByIDOrUserName - find users or by ID or by @name
func (users Users) FindByIDOrUserName(userName string) int {
	userID, err := strconv.Atoi(userName)
	if err == nil {
		if _, ok := users.list[userID]; !ok {
			userID = 0
		}
	} else {
		userName = cleanUserName(userName)
		userID = users.GetUserIDByName(userName)
	}

	return userID
}

// SendMessageToPrivate - send message to user to private chat
func (users Users) SendMessageToPrivate(messageSignal chan<- BotMessage, userID int, message string) bool {
	if user, ok := users.list[userID]; ok && user.PrivateChatID > 0 {
		sendMessage(messageSignal, user.PrivateChatID, message)
		return true
	}
	return false
}

// LoadFromDB - load users list from json file
func (users *Users) LoadFromDB(usersDBFile string) {
	usersList := UsersDB{}

	fileNamePath := getDBFilePath(usersDBFile, false)
	usersJSON, err := ioutil.ReadFile(fileNamePath)
	if err == nil {
		if err = json.Unmarshal(usersJSON, &usersList); err == nil {
			for _, user := range usersList.Users {
				users.list[user.UserID] = &user
			}
		}
	}
	if err == nil {
		log.Printf("Loaded usersDB json from: %s", fileNamePath)
	} else {
		log.Printf("Load usersDB (%s) error: %s", fileNamePath, err)
	}

	users.needSaveDB = false
}

// SaveToDB - save users list to json file
func (users *Users) SaveToDB(usersDBFile string) {
	if users.needSaveDB {
		usersList := UsersDB{
			Users:    []User{},
			DateTime: time.Now(),
		}
		for _, user := range users.list {
			usersList.Users = append(usersList.Users, *user)
		}

		fileNamePath := getDBFilePath(usersDBFile, true)
		json, err := json.MarshalIndent(usersList, "", "  ")
		if err == nil {
			err = ioutil.WriteFile(fileNamePath, json, 0644)
		}

		if err == nil {
			log.Printf("Saved usersDB json to: %s", fileNamePath)
		} else {
			log.Printf("Save usersDB (%s) error: %s", fileNamePath, err)
		}

		users.needSaveDB = false
	}
}
<|endoftext|>


<commit_before>package libipvs

import (
	"fmt"
	"net"
	"syscall"

	"unsafe"

	"github.com/hkwi/nlgo"
)

// Helper to build an nlgo.Attr
func nlattr(typ uint16, value nlgo.NlaValue) nlgo.Attr {
	return nlgo.Attr{Header: syscall.NlAttr{Type: typ}, Value: value}
}

const sizeOfFlags = 0x8

func (f *Flags) unpack(value nlgo.Binary) error {
	if len(value) != sizeOfFlags {
		return fmt.Errorf("ipvs: unexpected flags=%v", value)
	}
	var buf [sizeOfFlags]byte
	copy(buf[:], value)
	tmp := (*Flags)(unsafe.Pointer(&buf))
	f.Flags = tmp.Flags
	f.Mask = tmp.Mask
	return nil
}

func (f *Flags) pack() nlgo.Binary {
	buf := make([]byte, sizeOfFlags)
	copy(buf, (*[sizeOfFlags]byte)(unsafe.Pointer(f))[:])
	return nlgo.Binary(buf)
}

// Helpers for net.IP <-> nlgo.Binary
func unpackAddr(value nlgo.Binary, af AddressFamily) (net.IP, error) {
	buf := ([]byte)(value)
	size := 0

	switch af {
	case syscall.AF_INET:
		size = 4
	case syscall.AF_INET6:
		size = 16
	default:
		return nil, fmt.Errorf("ipvs: unknown af=%d addr=%v", af, buf)
	}

	if size > len(buf) {
		return nil, fmt.Errorf("ipvs: short af=%d addr=%v", af, buf)
	}

	return (net.IP)(buf[:size]), nil
}

func packAddr(af AddressFamily, addr net.IP) nlgo.Binary {
	var ip net.IP

	switch af {
	case syscall.AF_INET:
		ip = addr.To4()
	case syscall.AF_INET6:
		ip = addr.To16()
	default:
		panic(fmt.Errorf("ipvs:packAddr: unknown af=%d addr=%v", af, addr))
	}

	if ip == nil {
		panic(fmt.Errorf("ipvs:packAddr: invalid af=%d addr=%v", af, addr))
	}

	return (nlgo.Binary)(ip)
}

// Helpers for uint16 port <-> nlgo.U16
func htons(value uint16) uint16 {
	return ((value & 0x00ff) << 8) | ((value & 0xff00) >> 8)
}
func ntohs(value uint16) uint16 {
	return ((value & 0x00ff) << 8) | ((value & 0xff00) >> 8)
}

func unpackPort(val nlgo.U16) uint16 {
	return ntohs((uint16)(val))
}
func packPort(port uint16) nlgo.U16 {
	return nlgo.U16(htons(port))
}

func unpackService(attrs nlgo.AttrMap) (Service, error) {
	var service Service

	var addr nlgo.Binary
	var flags nlgo.Binary

	for _, attr := range attrs.Slice() {
		switch attr.Field() {
		case IPVS_SVC_ATTR_AF:
			service.AddressFamily = (AddressFamily)(attr.Value.(nlgo.U16))
		case IPVS_SVC_ATTR_PROTOCOL:
			service.Protocol = (Protocol)(attr.Value.(nlgo.U16))
		case IPVS_SVC_ATTR_ADDR:
			addr = attr.Value.(nlgo.Binary)
		case IPVS_SVC_ATTR_PORT:
			service.Port = unpackPort(attr.Value.(nlgo.U16))
		case IPVS_SVC_ATTR_FWMARK:
			service.FWMark = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_SVC_ATTR_SCHED_NAME:
			service.SchedName = (string)(attr.Value.(nlgo.NulString))
		case IPVS_SVC_ATTR_FLAGS:
			flags = attr.Value.(nlgo.Binary)
		case IPVS_SVC_ATTR_TIMEOUT:
			service.Timeout = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_SVC_ATTR_NETMASK:
			service.Netmask = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_SVC_ATTR_STATS:
			service.Stats = unpackStats(attr)
		}
	}

	// TOTE: ipvs Service with Fwmarks has no Address, so we just ignore the error
	if addrIP, err := unpackAddr(addr, service.AddressFamily); err != nil && service.FWMark == 0 {
		return service, fmt.Errorf("ipvs:Service.unpack: addr: %s", err)
	} else {
		service.Address = addrIP
	}

	if err := service.Flags.unpack(flags); err != nil {
		return service, fmt.Errorf("ipvs:Service.unpack: flags: %s", err)
	}

	return service, nil
}

func unpackDest(attrs nlgo.AttrMap, serverAddressFamily AddressFamily) (Destination, error) {
	var dest Destination
	var addr []byte

	for _, attr := range attrs.Slice() {
		switch attr.Field() {
		case IPVS_DEST_ATTR_ADDR_FAMILY:
			dest.AddressFamily = (AddressFamily)(attr.Value.(nlgo.U16))
		case IPVS_DEST_ATTR_ADDR:
			addr = ([]byte)(attr.Value.(nlgo.Binary))
		case IPVS_DEST_ATTR_PORT:
			dest.Port = unpackPort(attr.Value.(nlgo.U16))
		case IPVS_DEST_ATTR_FWD_METHOD:
			dest.FwdMethod = (FwdMethod)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_WEIGHT:
			dest.Weight = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_U_THRESH:
			dest.UThresh = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_L_THRESH:
			dest.LThresh = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_ACTIVE_CONNS:
			dest.ActiveConns = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_INACT_CONNS:
			dest.InactConns = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_PERSIST_CONNS:
			dest.PersistConns = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_STATS:
			dest.Stats = unpackStats(attr)
		}
	}
	// Linux kernel prior v3.18-rc1 does not have the af (address family) field
	// in ip_vs_dest_user_kern, so use the address family in ip_vs_service_user_kern.
	// https://github.com/torvalds/linux/commit/6cff339bbd5f9eda7a5e8a521f91a88d046e6d0c
	if dest.AddressFamily == 0 {
		dest.AddressFamily = serverAddressFamily
	}

	if addrIP, err := unpackAddr(addr, dest.AddressFamily); err != nil {
		return dest, fmt.Errorf("ipvs:Dest.unpack: addr: %s", err)
	} else {
		dest.Address = addrIP
	}

	return dest, nil
}

func unpackInfo(attrs nlgo.AttrMap) (info Info, err error) {
	for _, attr := range attrs.Slice() {
		switch attr.Field() {
		case IPVS_INFO_ATTR_VERSION:
			info.Version = (Version)(attr.Value.(nlgo.U32))
		case IPVS_INFO_ATTR_CONN_TAB_SIZE:
			info.ConnTabSize = (uint32)(attr.Value.(nlgo.U32))
		}
	}

	return
}

func unpackStats(attrs nlgo.Attr) Stats {
	var stats Stats
	for _, attr := range attrs.Value.(nlgo.AttrMap).Slice() {
		switch attr.Field() {
		case IPVS_STATS_ATTR_CONNS:
			stats.Connections = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_INPKTS: /* incoming packets */
			stats.PacketsIn = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_OUTPKTS: /* outgoing packets */
			stats.PacketsOut = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_INBYTES: /* incoming bytes */
			stats.BytesIn = (uint64)(attr.Value.(nlgo.U64))
		case IPVS_STATS_ATTR_OUTBYTES: /* outgoing bytes */
			stats.BytesOut = (uint64)(attr.Value.(nlgo.U64))
		case IPVS_STATS_ATTR_CPS: /* current connection rate */
			stats.CPS = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_INPPS: /* current in packet rate */
			stats.PPSIn = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_OUTPPS: /* current out packet rate */
			stats.PPSOut = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_INBPS: /* current in byte rate */
			stats.BPSIn = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_OUTBPS: /* current out byte rate */
			stats.BPSOut = (uint32)(attr.Value.(nlgo.U32))
		}
	}

	return stats
}
<commit_msg>Fix typo<commit_after>package libipvs

import (
	"fmt"
	"net"
	"syscall"

	"unsafe"

	"github.com/hkwi/nlgo"
)

// Helper to build an nlgo.Attr
func nlattr(typ uint16, value nlgo.NlaValue) nlgo.Attr {
	return nlgo.Attr{Header: syscall.NlAttr{Type: typ}, Value: value}
}

const sizeOfFlags = 0x8

func (f *Flags) unpack(value nlgo.Binary) error {
	if len(value) != sizeOfFlags {
		return fmt.Errorf("ipvs: unexpected flags=%v", value)
	}
	var buf [sizeOfFlags]byte
	copy(buf[:], value)
	tmp := (*Flags)(unsafe.Pointer(&buf))
	f.Flags = tmp.Flags
	f.Mask = tmp.Mask
	return nil
}

func (f *Flags) pack() nlgo.Binary {
	buf := make([]byte, sizeOfFlags)
	copy(buf, (*[sizeOfFlags]byte)(unsafe.Pointer(f))[:])
	return nlgo.Binary(buf)
}

// Helpers for net.IP <-> nlgo.Binary
func unpackAddr(value nlgo.Binary, af AddressFamily) (net.IP, error) {
	buf := ([]byte)(value)
	size := 0

	switch af {
	case syscall.AF_INET:
		size = 4
	case syscall.AF_INET6:
		size = 16
	default:
		return nil, fmt.Errorf("ipvs: unknown af=%d addr=%v", af, buf)
	}

	if size > len(buf) {
		return nil, fmt.Errorf("ipvs: short af=%d addr=%v", af, buf)
	}

	return (net.IP)(buf[:size]), nil
}

func packAddr(af AddressFamily, addr net.IP) nlgo.Binary {
	var ip net.IP

	switch af {
	case syscall.AF_INET:
		ip = addr.To4()
	case syscall.AF_INET6:
		ip = addr.To16()
	default:
		panic(fmt.Errorf("ipvs:packAddr: unknown af=%d addr=%v", af, addr))
	}

	if ip == nil {
		panic(fmt.Errorf("ipvs:packAddr: invalid af=%d addr=%v", af, addr))
	}

	return (nlgo.Binary)(ip)
}

// Helpers for uint16 port <-> nlgo.U16
func htons(value uint16) uint16 {
	return ((value & 0x00ff) << 8) | ((value & 0xff00) >> 8)
}
func ntohs(value uint16) uint16 {
	return ((value & 0x00ff) << 8) | ((value & 0xff00) >> 8)
}

func unpackPort(val nlgo.U16) uint16 {
	return ntohs((uint16)(val))
}
func packPort(port uint16) nlgo.U16 {
	return nlgo.U16(htons(port))
}

func unpackService(attrs nlgo.AttrMap) (Service, error) {
	var service Service

	var addr nlgo.Binary
	var flags nlgo.Binary

	for _, attr := range attrs.Slice() {
		switch attr.Field() {
		case IPVS_SVC_ATTR_AF:
			service.AddressFamily = (AddressFamily)(attr.Value.(nlgo.U16))
		case IPVS_SVC_ATTR_PROTOCOL:
			service.Protocol = (Protocol)(attr.Value.(nlgo.U16))
		case IPVS_SVC_ATTR_ADDR:
			addr = attr.Value.(nlgo.Binary)
		case IPVS_SVC_ATTR_PORT:
			service.Port = unpackPort(attr.Value.(nlgo.U16))
		case IPVS_SVC_ATTR_FWMARK:
			service.FWMark = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_SVC_ATTR_SCHED_NAME:
			service.SchedName = (string)(attr.Value.(nlgo.NulString))
		case IPVS_SVC_ATTR_FLAGS:
			flags = attr.Value.(nlgo.Binary)
		case IPVS_SVC_ATTR_TIMEOUT:
			service.Timeout = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_SVC_ATTR_NETMASK:
			service.Netmask = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_SVC_ATTR_STATS:
			service.Stats = unpackStats(attr)
		}
	}

	// NOTE: ipvs Service with Fwmarks has no Address, so we just ignore the error
	if addrIP, err := unpackAddr(addr, service.AddressFamily); err != nil && service.FWMark == 0 {
		return service, fmt.Errorf("ipvs:Service.unpack: addr: %s", err)
	} else {
		service.Address = addrIP
	}

	if err := service.Flags.unpack(flags); err != nil {
		return service, fmt.Errorf("ipvs:Service.unpack: flags: %s", err)
	}

	return service, nil
}

func unpackDest(attrs nlgo.AttrMap, serverAddressFamily AddressFamily) (Destination, error) {
	var dest Destination
	var addr []byte

	for _, attr := range attrs.Slice() {
		switch attr.Field() {
		case IPVS_DEST_ATTR_ADDR_FAMILY:
			dest.AddressFamily = (AddressFamily)(attr.Value.(nlgo.U16))
		case IPVS_DEST_ATTR_ADDR:
			addr = ([]byte)(attr.Value.(nlgo.Binary))
		case IPVS_DEST_ATTR_PORT:
			dest.Port = unpackPort(attr.Value.(nlgo.U16))
		case IPVS_DEST_ATTR_FWD_METHOD:
			dest.FwdMethod = (FwdMethod)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_WEIGHT:
			dest.Weight = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_U_THRESH:
			dest.UThresh = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_L_THRESH:
			dest.LThresh = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_ACTIVE_CONNS:
			dest.ActiveConns = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_INACT_CONNS:
			dest.InactConns = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_PERSIST_CONNS:
			dest.PersistConns = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_DEST_ATTR_STATS:
			dest.Stats = unpackStats(attr)
		}
	}
	// Linux kernel prior v3.18-rc1 does not have the af (address family) field
	// in ip_vs_dest_user_kern, so use the address family in ip_vs_service_user_kern.
	// https://github.com/torvalds/linux/commit/6cff339bbd5f9eda7a5e8a521f91a88d046e6d0c
	if dest.AddressFamily == 0 {
		dest.AddressFamily = serverAddressFamily
	}

	if addrIP, err := unpackAddr(addr, dest.AddressFamily); err != nil {
		return dest, fmt.Errorf("ipvs:Dest.unpack: addr: %s", err)
	} else {
		dest.Address = addrIP
	}

	return dest, nil
}

func unpackInfo(attrs nlgo.AttrMap) (info Info, err error) {
	for _, attr := range attrs.Slice() {
		switch attr.Field() {
		case IPVS_INFO_ATTR_VERSION:
			info.Version = (Version)(attr.Value.(nlgo.U32))
		case IPVS_INFO_ATTR_CONN_TAB_SIZE:
			info.ConnTabSize = (uint32)(attr.Value.(nlgo.U32))
		}
	}

	return
}

func unpackStats(attrs nlgo.Attr) Stats {
	var stats Stats
	for _, attr := range attrs.Value.(nlgo.AttrMap).Slice() {
		switch attr.Field() {
		case IPVS_STATS_ATTR_CONNS:
			stats.Connections = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_INPKTS: /* incoming packets */
			stats.PacketsIn = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_OUTPKTS: /* outgoing packets */
			stats.PacketsOut = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_INBYTES: /* incoming bytes */
			stats.BytesIn = (uint64)(attr.Value.(nlgo.U64))
		case IPVS_STATS_ATTR_OUTBYTES: /* outgoing bytes */
			stats.BytesOut = (uint64)(attr.Value.(nlgo.U64))
		case IPVS_STATS_ATTR_CPS: /* current connection rate */
			stats.CPS = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_INPPS: /* current in packet rate */
			stats.PPSIn = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_OUTPPS: /* current out packet rate */
			stats.PPSOut = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_INBPS: /* current in byte rate */
			stats.BPSIn = (uint32)(attr.Value.(nlgo.U32))
		case IPVS_STATS_ATTR_OUTBPS: /* current out byte rate */
			stats.BPSOut = (uint32)(attr.Value.(nlgo.U32))
		}
	}

	return stats
}
<|endoftext|>


<commit_before>package forms

import (
	"bytes"
	"github.com/kirves/go-form-it/fields"
	"html/template"
	"reflect"
)

// FormElement interface defines a form object (usually a Field or a FieldSet) that can be rendered as a template.HTML object.
type FormElement interface {
	Render() template.HTML
}

// Render executes the internal template and renders the form, returning the result as a template.HTML object embeddable
// in any other template.
func (f *Form) Render() template.HTML {
	var s string
	buf := bytes.NewBufferString(s)
	data := map[string]interface{}{
		"fields":  f.fields,
		"classes": f.class,
		"id":      f.id,
		"params":  f.params,
		"css":     f.css,
		"method":  f.method,
		"action":  f.action,
	}
	err := f.template.Execute(buf, data)
	if err != nil {
		panic(err)
	}
	return template.HTML(buf.String())
}

// Elements adds the provided elements to the form.
func (f *Form) Elements(elems ...FormElement) *Form {
	for _, e := range elems {
		t := reflect.TypeOf(e)
		switch {
		case t.Implements(reflect.TypeOf((*fields.FieldInterface)(nil)).Elem()):
			f.addField(e.(fields.FieldInterface))
		case reflect.ValueOf(e).Type().String() == "*forms.FieldSetType":
			f.addFieldSet(e.(*FieldSetType))
		}
	}
	return f
}

func (f *Form) addField(field fields.FieldInterface) *Form {
	field.SetStyle(f.style)
	f.fields = append(f.fields, field)
	f.fieldMap[field.Name()] = len(f.fields) - 1
	return f
}

func (f *Form) addFieldSet(fs *FieldSetType) *Form {
	for _, v := range fs.fields {
		v.SetStyle(f.style)
	}
	f.fields = append(f.fields, fs)
	f.fieldMap[fs.Name()] = len(f.fields) - 1
	return f
}

// RemoveElement removes an element (identified by name) from the Form.
func (f *Form) RemoveElement(name string) *Form {
	ind, ok := f.fieldMap[name]
	if !ok {
		return f
	}
	delete(f.fieldMap, name)
	f.fields = append(f.fields[:ind], f.fields[ind+1:]...)
	return f
}

// AddClass associates the provided class to the Form.
func (f *Form) AddClass(class string) *Form {
	f.class = append(f.class, class)
	return f
}

// RemoveClass removes the given class (if present) from the Form.
func (f *Form) RemoveClass(class string) *Form {
	ind := -1
	for i, v := range f.class {
		if v == class {
			ind = i
			break
		}
	}

	if ind != -1 {
		f.class = append(f.class[:ind], f.class[ind+1:]...)
	}
	return f
}

// SetId set the given id to the form.
func (f *Form) SetId(id string) *Form {
	f.id = id
	return f
}

// SetParam adds the given key-value pair to form parameters list.
func (f *Form) SetParam(key, value string) *Form {
	f.params[key] = value
	return f
}

// DeleteParm removes the parameter identified by key from form parameters list.
func (f *Form) DeleteParam(key string) *Form {
	delete(f.params, key)
	return f
}

// AddCss add a CSS value (in the form of option-value - e.g.: border - auto) to the form.
func (f *Form) AddCss(key, value string) *Form {
	f.css[key] = value
	return f
}

// RemoveCss removes CSS style from the form.
func (f *Form) RemoveCss(key string) *Form {
	delete(f.css, key)
	return f
}
<commit_msg>Field method<commit_after>package forms

import (
	"bytes"
	"github.com/kirves/go-form-it/fields"
	"html/template"
	"reflect"
)

// FormElement interface defines a form object (usually a Field or a FieldSet) that can be rendered as a template.HTML object.
type FormElement interface {
	Render() template.HTML
}

// Render executes the internal template and renders the form, returning the result as a template.HTML object embeddable
// in any other template.
func (f *Form) Render() template.HTML {
	var s string
	buf := bytes.NewBufferString(s)
	data := map[string]interface{}{
		"fields":  f.fields,
		"classes": f.class,
		"id":      f.id,
		"params":  f.params,
		"css":     f.css,
		"method":  f.method,
		"action":  f.action,
	}
	err := f.template.Execute(buf, data)
	if err != nil {
		panic(err)
	}
	return template.HTML(buf.String())
}

// Elements adds the provided elements to the form.
func (f *Form) Elements(elems ...FormElement) *Form {
	for _, e := range elems {
		t := reflect.TypeOf(e)
		switch {
		case t.Implements(reflect.TypeOf((*fields.FieldInterface)(nil)).Elem()):
			f.addField(e.(fields.FieldInterface))
		case reflect.ValueOf(e).Type().String() == "*forms.FieldSetType":
			f.addFieldSet(e.(*FieldSetType))
		}
	}
	return f
}

func (f *Form) addField(field fields.FieldInterface) *Form {
	field.SetStyle(f.style)
	f.fields = append(f.fields, field)
	f.fieldMap[field.Name()] = len(f.fields) - 1
	return f
}

func (f *Form) addFieldSet(fs *FieldSetType) *Form {
	for _, v := range fs.fields {
		v.SetStyle(f.style)
	}
	f.fields = append(f.fields, fs)
	f.fieldMap[fs.Name()] = len(f.fields) - 1
	return f
}

// RemoveElement removes an element (identified by name) from the Form.
func (f *Form) RemoveElement(name string) *Form {
	ind, ok := f.fieldMap[name]
	if !ok {
		return f
	}
	delete(f.fieldMap, name)
	f.fields = append(f.fields[:ind], f.fields[ind+1:]...)
	return f
}

// AddClass associates the provided class to the Form.
func (f *Form) AddClass(class string) *Form {
	f.class = append(f.class, class)
	return f
}

// RemoveClass removes the given class (if present) from the Form.
func (f *Form) RemoveClass(class string) *Form {
	ind := -1
	for i, v := range f.class {
		if v == class {
			ind = i
			break
		}
	}

	if ind != -1 {
		f.class = append(f.class[:ind], f.class[ind+1:]...)
	}
	return f
}

// SetId set the given id to the form.
func (f *Form) SetId(id string) *Form {
	f.id = id
	return f
}

// SetParam adds the given key-value pair to form parameters list.
func (f *Form) SetParam(key, value string) *Form {
	f.params[key] = value
	return f
}

// DeleteParm removes the parameter identified by key from form parameters list.
func (f *Form) DeleteParam(key string) *Form {
	delete(f.params, key)
	return f
}

// AddCss add a CSS value (in the form of option-value - e.g.: border - auto) to the form.
func (f *Form) AddCss(key, value string) *Form {
	f.css[key] = value
	return f
}

// RemoveCss removes CSS style from the form.
func (f *Form) RemoveCss(key string) *Form {
	delete(f.css, key)
	return f
}

// Field returns the field identified by name. It returns an empty field if it is missing.
func (f *Form) Field(name string) fields.FieldInterface {
	ind, ok := f.fieldMap[name]
	if !ok || !reflect.TypeOf(f.fields[ind]).Implements(reflect.TypeOf((*fields.FieldInterface)(nil)).Elem()) {
		return &fields.Field{}
	}
	return f.fields[ind].(fields.FieldInterface)
}
<|endoftext|>