<commit_before>package radius

import (
	"crypto"
	"crypto/hmac"
	_ "crypto/md5"
	"crypto/rand"
	"encoding/binary"
	"errors"
	"fmt"
	"net"
	"strconv"
)

var ErrMessageAuthenticatorCheckFail = fmt.Errorf("RADIUS Response-Authenticator verification failed")

type Packet struct {
	Secret        string
	Code          PacketCode
	Identifier    uint8
	Authenticator [16]byte
	AVPs          []AVP
}

func (p *Packet) Copy() *Packet {
	outP := &Packet{
		Secret:        p.Secret,
		Code:          p.Code,
		Identifier:    p.Identifier,
		Authenticator: p.Authenticator, //这个应该是拷贝
	}
	outP.AVPs = make([]AVP, len(p.AVPs))
	for i := range p.AVPs {
		outP.AVPs[i] = p.AVPs[i].Copy()
	}
	return outP
}

//此方法保证不修改包的内容
func (p *Packet) Encode() (b []byte, err error) {
	p = p.Copy()
	p.SetAVP(AVP{
		Type:  MessageAuthenticator,
		Value: make([]byte, 16),
	})
	if p.Code == AccessRequest {
		_, err := rand.Read(p.Authenticator[:])
		if err != nil {
			return nil, err
		}
	}
	//TODO request的时候重新计算密码
	b, err = p.encodeNoHash()
	if err != nil {
		return
	}
	//计算Message-Authenticator,Message-Authenticator被放在最后面
	hasher := hmac.New(crypto.MD5.New, []byte(p.Secret))
	hasher.Write(b)
	copy(b[len(b)-16:len(b)], hasher.Sum(nil))

	// fix up the authenticator
	// handle request and response stuff.
	// here only handle response part.
	switch p.Code {
	case AccessRequest:
	case AccessAccept, AccessReject, AccessChallenge, AccountingRequest, AccountingResponse:
		//rfc2865 page 15 Response Authenticator
		//rfc2866 page 6 Response Authenticator
		//rfc2866 page 6 Request Authenticator
		hasher := crypto.Hash(crypto.MD5).New()
		hasher.Write(b)
		hasher.Write([]byte(p.Secret))
		copy(b[4:20], hasher.Sum(nil))
	default:
		return nil, fmt.Errorf("not handle p.Code %d", p.Code)
	}

	return b, err
}

func (p *Packet) encodeNoHash() (b []byte, err error) {
	b = make([]byte, 4096)
	b[0] = uint8(p.Code)
	b[1] = uint8(p.Identifier)
	copy(b[4:20], p.Authenticator[:])
	written := 20
	bb := b[20:]
	for i, _ := range p.AVPs {
		n, err := p.AVPs[i].Encode(bb)
		written += n
		if err != nil {
			return nil, err
		}
		bb = bb[n:]
	}
	binary.BigEndian.PutUint16(b[2:4], uint16(written))
	return b[:written], nil
}

func (p *Packet) HasAVP(attrType AttributeType) bool {
	for i, _ := range p.AVPs {
		if p.AVPs[i].Type == attrType {
			return true
		}
	}
	return false
}

/*
func (p *Packet) Attributes(attrType AttributeType) []*AVP {
	ret := []*AVP(nil)
	for i, _ := range p.AVPs {
		if p.AVPs[i].Type == attrType {
			ret = append(ret, &p.AVPs[i])
		}
	}
	return ret
}
*/

//get one avp
func (p *Packet) GetAVP(attrType AttributeType) *AVP {
	for i := range p.AVPs {
		if p.AVPs[i].Type == attrType {
			return &p.AVPs[i]
		}
	}
	return nil
}

//set one avp,remove all other same type
func (p *Packet) SetAVP(avp AVP) {
	p.DeleteOneType(avp.Type)
	p.AddAVP(avp)
}

func (p *Packet) AddAVP(avp AVP) {
	p.AVPs = append(p.AVPs, avp)
}

//删除一个AVP
func (p *Packet) DeleteAVP(avp *AVP) {
	for i := range p.AVPs {
		if &(p.AVPs[i]) == avp {
			for j := i; j < len(p.AVPs)-1; j++ {
				p.AVPs[j] = p.AVPs[j+1]
			}
			p.AVPs = p.AVPs[:len(p.AVPs)-1]
			break
		}
	}
	return
}

//delete all avps with this type
func (p *Packet) DeleteOneType(attrType AttributeType) {
	for i := 0; i < len(p.AVPs); i++ {
		if p.AVPs[i].Type == attrType {
			for j := i; j < len(p.AVPs)-1; j++ {
				p.AVPs[j] = p.AVPs[j+1]
			}
			p.AVPs = p.AVPs[:len(p.AVPs)-1]
			i--
			break
		}
	}
	return
}

/*
func (p *Packet) Valid() bool {
	switch p.Code {
	case AccessRequest:
		if !(p.Has(NASIPAddress) || p.Has(NASIdentifier)) {
			return false
		}

		if p.Has(CHAPPassword) && p.Has(UserPassword) {
			return false
		}
		return true
	case AccessAccept:
		return true
	case AccessReject:
		return true
	case AccountingRequest:
		return true
	case AccountingResponse:
		return true
	case AccessChallenge:
		return true
	case StatusServer:
		return true
	case StatusClient:
		return true
	case Reserved:
		return true
	}
	return true
}
*/

func (p *Packet) Reply() *Packet {
	pac := new(Packet)
	pac.Authenticator = p.Authenticator
	pac.Identifier = p.Identifier
	pac.Secret = p.Secret
	return pac
}

func (p *Packet) Send(c net.PacketConn, addr net.Addr) error {
	buf, err := p.Encode()
	if err != nil {
		return err
	}

	_, err = c.WriteTo(buf, addr)
	return err
}

func DecodePacket(Secret string, buf []byte) (p *Packet, err error) {
	p = &Packet{Secret: Secret}
	p.Code = PacketCode(buf[0])
	p.Identifier = buf[1]
	copy(p.Authenticator[:], buf[4:20])
	//read attributes
	b := buf[20:]
	for len(b) >= 2 {
		length := uint8(b[1])
		if int(length) > len(b) {
			return nil, errors.New("invalid length")
		}
		attr := AVP{}
		attr.Type = AttributeType(b[0])
		attr.Value = append(attr.Value, b[2:length]...)
		p.AVPs = append(p.AVPs, attr)
		b = b[length:]
	}
	//验证Message-Authenticator,并且通过测试验证此处算法是正确的
	err = p.checkMessageAuthenticator()
	if err != nil {
		return p, err
	}
	return p, nil
}

//如果没有MessageAuthenticator也算通过
func (p *Packet) checkMessageAuthenticator() (err error) {
	Authenticator := p.GetAVP(MessageAuthenticator)
	if Authenticator == nil {
		return nil
	}
	AuthenticatorValue := Authenticator.Value
	defer func() { Authenticator.Value = AuthenticatorValue }()
	Authenticator.Value = make([]byte, 16)
	content, err := p.encodeNoHash()
	if err != nil {
		return err
	}
	hasher := hmac.New(crypto.MD5.New, []byte(p.Secret))
	hasher.Write(content)
	if !hmac.Equal(hasher.Sum(nil), AuthenticatorValue) {
		return ErrMessageAuthenticatorCheckFail
	}
	return nil
}

func (p *Packet) String() string {
	s := "Code: " + p.Code.String() + "\n" +
		"Identifier: " + strconv.Itoa(int(p.Identifier)) + "\n" +
		"Authenticator: " + fmt.Sprintf("%#v", p.Authenticator) + "\n"
	for _, avp := range p.AVPs {
		s += avp.StringWithPacket(p) + "\n"
	}
	return s
}

func (p *Packet) GetUsername() (username string) {
	avp := p.GetAVP(UserName)
	if avp == nil {
		return ""
	}
	return avp.Decode(p).(string)
}
func (p *Packet) GetPassword() (password string) {
	avp := p.GetAVP(UserPassword)
	if avp == nil {
		return ""
	}
	return avp.Decode(p).(string)
}

func (p *Packet) GetNasIpAddress() (ip net.IP) {
	avp := p.GetAVP(NASIPAddress)
	if avp == nil {
		return nil
	}
	return avp.Decode(p).(net.IP)
}

func (p *Packet) GetAcctStatusType() AcctStatusTypeEnum {
	avp := p.GetAVP(AcctStatusType)
	if avp == nil {
		return AcctStatusTypeEnum(0)
	}
	return avp.Decode(p).(AcctStatusTypeEnum)
}

func (p *Packet) GetAcctSessionId() string {
	avp := p.GetAVP(AcctSessionId)
	if avp == nil {
		return ""
	}
	return avp.Decode(p).(string)
}

func (p *Packet) GetAcctTotalOutputOctets() uint64 {
	out := uint64(0)
	avp := p.GetAVP(AcctOutputOctets)
	if avp != nil {
		out += uint64(avp.Decode(p).(uint32))
	}
	avp = p.GetAVP(AcctOutputGigawords)
	if avp != nil {
		out += uint64(avp.Decode(p).(uint32))*2 ^ 32
	}
	return out
}

func (p *Packet) GetAcctTotalInputOctets() uint64 {
	out := uint64(0)
	avp := p.GetAVP(AcctInputOctets)
	if avp != nil {
		out += uint64(avp.Decode(p).(uint32))
	}
	avp = p.GetAVP(AcctInputGigawords)
	if avp != nil {
		out += uint64(avp.Decode(p).(uint32))*2 ^ 32
	}
	return out
}

// it is ike_id in strongswan client
func (p *Packet) GetNASPort() uint32 {
	avp := p.GetAVP(NASPort)
	if avp == nil {
		return 0
	}
	return avp.Decode(p).(uint32)
}

func (p *Packet) GetNASIdentifier() string {
	avp := p.GetAVP(NASIdentifier)
	if avp == nil {
		return ""
	}
	return avp.Decode(p).(string)
}

func (p *Packet) GetEAPMessage() *EapPacket {
	avp := p.GetAVP(EAPMessage)
	if avp == nil {
		return nil
	}
	return avp.Decode(p).(*EapPacket)
}
<commit_msg>Fixed accounting input/output counts<commit_after>package radius

import (
	"crypto"
	"crypto/hmac"
	_ "crypto/md5"
	"crypto/rand"
	"encoding/binary"
	"errors"
	"fmt"
	"net"
	"strconv"
)

var ErrMessageAuthenticatorCheckFail = fmt.Errorf("RADIUS Response-Authenticator verification failed")

type Packet struct {
	Secret        string
	Code          PacketCode
	Identifier    uint8
	Authenticator [16]byte
	AVPs          []AVP
}

func (p *Packet) Copy() *Packet {
	outP := &Packet{
		Secret:        p.Secret,
		Code:          p.Code,
		Identifier:    p.Identifier,
		Authenticator: p.Authenticator, //这个应该是拷贝
	}
	outP.AVPs = make([]AVP, len(p.AVPs))
	for i := range p.AVPs {
		outP.AVPs[i] = p.AVPs[i].Copy()
	}
	return outP
}

//此方法保证不修改包的内容
func (p *Packet) Encode() (b []byte, err error) {
	p = p.Copy()
	p.SetAVP(AVP{
		Type:  MessageAuthenticator,
		Value: make([]byte, 16),
	})
	if p.Code == AccessRequest {
		_, err := rand.Read(p.Authenticator[:])
		if err != nil {
			return nil, err
		}
	}
	//TODO request的时候重新计算密码
	b, err = p.encodeNoHash()
	if err != nil {
		return
	}
	//计算Message-Authenticator,Message-Authenticator被放在最后面
	hasher := hmac.New(crypto.MD5.New, []byte(p.Secret))
	hasher.Write(b)
	copy(b[len(b)-16:len(b)], hasher.Sum(nil))

	// fix up the authenticator
	// handle request and response stuff.
	// here only handle response part.
	switch p.Code {
	case AccessRequest:
	case AccessAccept, AccessReject, AccessChallenge, AccountingRequest, AccountingResponse:
		//rfc2865 page 15 Response Authenticator
		//rfc2866 page 6 Response Authenticator
		//rfc2866 page 6 Request Authenticator
		hasher := crypto.Hash(crypto.MD5).New()
		hasher.Write(b)
		hasher.Write([]byte(p.Secret))
		copy(b[4:20], hasher.Sum(nil))
	default:
		return nil, fmt.Errorf("not handle p.Code %d", p.Code)
	}

	return b, err
}

func (p *Packet) encodeNoHash() (b []byte, err error) {
	b = make([]byte, 4096)
	b[0] = uint8(p.Code)
	b[1] = uint8(p.Identifier)
	copy(b[4:20], p.Authenticator[:])
	written := 20
	bb := b[20:]
	for i, _ := range p.AVPs {
		n, err := p.AVPs[i].Encode(bb)
		written += n
		if err != nil {
			return nil, err
		}
		bb = bb[n:]
	}
	binary.BigEndian.PutUint16(b[2:4], uint16(written))
	return b[:written], nil
}

func (p *Packet) HasAVP(attrType AttributeType) bool {
	for i, _ := range p.AVPs {
		if p.AVPs[i].Type == attrType {
			return true
		}
	}
	return false
}

/*
func (p *Packet) Attributes(attrType AttributeType) []*AVP {
	ret := []*AVP(nil)
	for i, _ := range p.AVPs {
		if p.AVPs[i].Type == attrType {
			ret = append(ret, &p.AVPs[i])
		}
	}
	return ret
}
*/

//get one avp
func (p *Packet) GetAVP(attrType AttributeType) *AVP {
	for i := range p.AVPs {
		if p.AVPs[i].Type == attrType {
			return &p.AVPs[i]
		}
	}
	return nil
}

//set one avp,remove all other same type
func (p *Packet) SetAVP(avp AVP) {
	p.DeleteOneType(avp.Type)
	p.AddAVP(avp)
}

func (p *Packet) AddAVP(avp AVP) {
	p.AVPs = append(p.AVPs, avp)
}

//删除一个AVP
func (p *Packet) DeleteAVP(avp *AVP) {
	for i := range p.AVPs {
		if &(p.AVPs[i]) == avp {
			for j := i; j < len(p.AVPs)-1; j++ {
				p.AVPs[j] = p.AVPs[j+1]
			}
			p.AVPs = p.AVPs[:len(p.AVPs)-1]
			break
		}
	}
	return
}

//delete all avps with this type
func (p *Packet) DeleteOneType(attrType AttributeType) {
	for i := 0; i < len(p.AVPs); i++ {
		if p.AVPs[i].Type == attrType {
			for j := i; j < len(p.AVPs)-1; j++ {
				p.AVPs[j] = p.AVPs[j+1]
			}
			p.AVPs = p.AVPs[:len(p.AVPs)-1]
			i--
			break
		}
	}
	return
}

/*
func (p *Packet) Valid() bool {
	switch p.Code {
	case AccessRequest:
		if !(p.Has(NASIPAddress) || p.Has(NASIdentifier)) {
			return false
		}

		if p.Has(CHAPPassword) && p.Has(UserPassword) {
			return false
		}
		return true
	case AccessAccept:
		return true
	case AccessReject:
		return true
	case AccountingRequest:
		return true
	case AccountingResponse:
		return true
	case AccessChallenge:
		return true
	case StatusServer:
		return true
	case StatusClient:
		return true
	case Reserved:
		return true
	}
	return true
}
*/

func (p *Packet) Reply() *Packet {
	pac := new(Packet)
	pac.Authenticator = p.Authenticator
	pac.Identifier = p.Identifier
	pac.Secret = p.Secret
	return pac
}

func (p *Packet) Send(c net.PacketConn, addr net.Addr) error {
	buf, err := p.Encode()
	if err != nil {
		return err
	}

	_, err = c.WriteTo(buf, addr)
	return err
}

func DecodePacket(Secret string, buf []byte) (p *Packet, err error) {
	p = &Packet{Secret: Secret}
	p.Code = PacketCode(buf[0])
	p.Identifier = buf[1]
	copy(p.Authenticator[:], buf[4:20])
	//read attributes
	b := buf[20:]
	for len(b) >= 2 {
		length := uint8(b[1])
		if int(length) > len(b) {
			return nil, errors.New("invalid length")
		}
		attr := AVP{}
		attr.Type = AttributeType(b[0])
		attr.Value = append(attr.Value, b[2:length]...)
		p.AVPs = append(p.AVPs, attr)
		b = b[length:]
	}
	//验证Message-Authenticator,并且通过测试验证此处算法是正确的
	err = p.checkMessageAuthenticator()
	if err != nil {
		return p, err
	}
	return p, nil
}

//如果没有MessageAuthenticator也算通过
func (p *Packet) checkMessageAuthenticator() (err error) {
	Authenticator := p.GetAVP(MessageAuthenticator)
	if Authenticator == nil {
		return nil
	}
	AuthenticatorValue := Authenticator.Value
	defer func() { Authenticator.Value = AuthenticatorValue }()
	Authenticator.Value = make([]byte, 16)
	content, err := p.encodeNoHash()
	if err != nil {
		return err
	}
	hasher := hmac.New(crypto.MD5.New, []byte(p.Secret))
	hasher.Write(content)
	if !hmac.Equal(hasher.Sum(nil), AuthenticatorValue) {
		return ErrMessageAuthenticatorCheckFail
	}
	return nil
}

func (p *Packet) String() string {
	s := "Code: " + p.Code.String() + "\n" +
		"Identifier: " + strconv.Itoa(int(p.Identifier)) + "\n" +
		"Authenticator: " + fmt.Sprintf("%#v", p.Authenticator) + "\n"
	for _, avp := range p.AVPs {
		s += avp.StringWithPacket(p) + "\n"
	}
	return s
}

func (p *Packet) GetUsername() (username string) {
	avp := p.GetAVP(UserName)
	if avp == nil {
		return ""
	}
	return avp.Decode(p).(string)
}
func (p *Packet) GetPassword() (password string) {
	avp := p.GetAVP(UserPassword)
	if avp == nil {
		return ""
	}
	return avp.Decode(p).(string)
}

func (p *Packet) GetNasIpAddress() (ip net.IP) {
	avp := p.GetAVP(NASIPAddress)
	if avp == nil {
		return nil
	}
	return avp.Decode(p).(net.IP)
}

func (p *Packet) GetAcctStatusType() AcctStatusTypeEnum {
	avp := p.GetAVP(AcctStatusType)
	if avp == nil {
		return AcctStatusTypeEnum(0)
	}
	return avp.Decode(p).(AcctStatusTypeEnum)
}

func (p *Packet) GetAcctSessionId() string {
	avp := p.GetAVP(AcctSessionId)
	if avp == nil {
		return ""
	}
	return avp.Decode(p).(string)
}

func (p *Packet) GetAcctTotalOutputOctets() uint64 {
	out := uint64(0)
	avp := p.GetAVP(AcctOutputOctets)
	if avp != nil {
		out += uint64(avp.Decode(p).(uint32))
	}
	avp = p.GetAVP(AcctOutputGigawords)
	if avp != nil {
		out += uint64(avp.Decode(p).(uint32)) << 32
	}
	return out
}

func (p *Packet) GetAcctTotalInputOctets() uint64 {
	out := uint64(0)
	avp := p.GetAVP(AcctInputOctets)
	if avp != nil {
		out += uint64(avp.Decode(p).(uint32))
	}
	avp = p.GetAVP(AcctInputGigawords)
	if avp != nil {
		out += uint64(avp.Decode(p).(uint32)) << 32
	}
	return out
}

// it is ike_id in strongswan client
func (p *Packet) GetNASPort() uint32 {
	avp := p.GetAVP(NASPort)
	if avp == nil {
		return 0
	}
	return avp.Decode(p).(uint32)
}

func (p *Packet) GetNASIdentifier() string {
	avp := p.GetAVP(NASIdentifier)
	if avp == nil {
		return ""
	}
	return avp.Decode(p).(string)
}

func (p *Packet) GetEAPMessage() *EapPacket {
	avp := p.GetAVP(EAPMessage)
	if avp == nil {
		return nil
	}
	return avp.Decode(p).(*EapPacket)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 network

import (
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

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

	"k8s.io/kubernetes/test/e2e/framework"
	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
	e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
	"k8s.io/kubernetes/test/images/agnhost/net/nat"
	imageutils "k8s.io/kubernetes/test/utils/image"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
)

var kubeProxyE2eImage = imageutils.GetE2EImage(imageutils.Agnhost)

var _ = SIGDescribe("Network", func() {
	const (
		testDaemonHTTPPort    = 11301
		testDaemonTCPPort     = 11302
		timeoutSeconds        = 10
		postFinTimeoutSeconds = 5
	)

	fr := framework.NewDefaultFramework("network")

	ginkgo.It("should set TCP CLOSE_WAIT timeout", func() {
		nodes, err := e2enode.GetBoundedReadySchedulableNodes(fr.ClientSet, 2)
		framework.ExpectNoError(err)
		if len(nodes.Items) < 2 {
			framework.Skipf(
				"Test requires >= 2 Ready nodes, but there are only %v nodes",
				len(nodes.Items))
		}

		ips := e2enode.CollectAddresses(nodes, v1.NodeInternalIP)

		type NodeInfo struct {
			node   *v1.Node
			name   string
			nodeIP string
		}

		clientNodeInfo := NodeInfo{
			node:   &nodes.Items[0],
			name:   nodes.Items[0].Name,
			nodeIP: ips[0],
		}

		serverNodeInfo := NodeInfo{
			node:   &nodes.Items[1],
			name:   nodes.Items[1].Name,
			nodeIP: ips[1],
		}

		zero := int64(0)

		// Some distributions (Ubuntu 16.04 etc.) don't support the proc file.
		_, err = e2essh.IssueSSHCommandWithResult(
			"ls /proc/net/nf_conntrack",
			framework.TestContext.Provider,
			clientNodeInfo.node)
		if err != nil && strings.Contains(err.Error(), "No such file or directory") {
			framework.Skipf("The node %s does not support /proc/net/nf_conntrack", clientNodeInfo.name)
		}
		framework.ExpectNoError(err)

		clientPodSpec := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "e2e-net-client",
				Namespace: fr.Namespace.Name,
				Labels:    map[string]string{"app": "e2e-net-client"},
			},
			Spec: v1.PodSpec{
				NodeName: clientNodeInfo.name,
				Containers: []v1.Container{
					{
						Name:            "e2e-net-client",
						Image:           kubeProxyE2eImage,
						ImagePullPolicy: "Always",
						Args: []string{
							"net", "--serve", fmt.Sprintf("0.0.0.0:%d", testDaemonHTTPPort),
						},
					},
				},
				TerminationGracePeriodSeconds: &zero,
			},
		}

		serverPodSpec := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "e2e-net-server",
				Namespace: fr.Namespace.Name,
				Labels:    map[string]string{"app": "e2e-net-server"},
			},
			Spec: v1.PodSpec{
				NodeName: serverNodeInfo.name,
				Containers: []v1.Container{
					{
						Name:            "e2e-net-server",
						Image:           kubeProxyE2eImage,
						ImagePullPolicy: "Always",
						Args: []string{
							"net",
							"--runner", "nat-closewait-server",
							"--options",
							fmt.Sprintf(`{"LocalAddr":"0.0.0.0:%v", "PostFindTimeoutSeconds":%v}`,
								testDaemonTCPPort,
								postFinTimeoutSeconds),
						},
						Ports: []v1.ContainerPort{
							{
								Name:          "tcp",
								ContainerPort: testDaemonTCPPort,
								HostPort:      testDaemonTCPPort,
							},
						},
					},
				},
				TerminationGracePeriodSeconds: &zero,
			},
		}

		ginkgo.By(fmt.Sprintf(
			"Launching a server daemon on node %v (node ip: %v, image: %v)",
			serverNodeInfo.name,
			serverNodeInfo.nodeIP,
			kubeProxyE2eImage))
		fr.PodClient().CreateSync(serverPodSpec)

		ginkgo.By(fmt.Sprintf(
			"Launching a client daemon on node %v (node ip: %v, image: %v)",
			clientNodeInfo.name,
			clientNodeInfo.nodeIP,
			kubeProxyE2eImage))
		fr.PodClient().CreateSync(clientPodSpec)

		ginkgo.By("Make client connect")

		options := nat.CloseWaitClientOptions{
			RemoteAddr: fmt.Sprintf("%v:%v",
				serverNodeInfo.nodeIP, testDaemonTCPPort),
			TimeoutSeconds:        timeoutSeconds,
			PostFinTimeoutSeconds: 0,
			LeakConnection:        true,
		}

		jsonBytes, err := json.Marshal(options)
		framework.ExpectNoError(err, "could not marshal")

		cmd := fmt.Sprintf(
			`curl -X POST http://localhost:%v/run/nat-closewait-client -d `+
				`'%v' 2>/dev/null`,
			testDaemonHTTPPort,
			string(jsonBytes))
		framework.RunHostCmdOrDie(fr.Namespace.Name, "e2e-net-client", cmd)

		<-time.After(time.Duration(1) * time.Second)

		ginkgo.By("Checking /proc/net/nf_conntrack for the timeout")
		// If test flakes occur here, then this check should be performed
		// in a loop as there may be a race with the client connecting.
		e2essh.IssueSSHCommandWithResult(
			fmt.Sprintf("sudo cat /proc/net/nf_conntrack | grep 'dport=%v'",
				testDaemonTCPPort),
			framework.TestContext.Provider,
			clientNodeInfo.node)

		// Timeout in seconds is available as the fifth column from
		// /proc/net/nf_conntrack.
		result, err := e2essh.IssueSSHCommandWithResult(
			fmt.Sprintf(
				"sudo cat /proc/net/nf_conntrack "+
					"| grep 'CLOSE_WAIT.*dst=%v.*dport=%v' "+
					"| tail -n 1"+
					"| awk '{print $5}' ",
				serverNodeInfo.nodeIP,
				testDaemonTCPPort),
			framework.TestContext.Provider,
			clientNodeInfo.node)
		framework.ExpectNoError(err)

		timeoutSeconds, err := strconv.Atoi(strings.TrimSpace(result.Stdout))
		framework.ExpectNoError(err)

		// These must be synchronized from the default values set in
		// pkg/apis/../defaults.go ConntrackTCPCloseWaitTimeout. The
		// current defaults are hidden in the initialization code.
		const epsilonSeconds = 60
		const expectedTimeoutSeconds = 60 * 60

		framework.Logf("conntrack entry timeout was: %v, expected: %v",
			timeoutSeconds, expectedTimeoutSeconds)

		gomega.Expect(math.Abs(float64(timeoutSeconds - expectedTimeoutSeconds))).Should(
			gomega.BeNumerically("<", (epsilonSeconds)))
	})

	// Regression test for #74839, where:
	// Packets considered INVALID by conntrack are now dropped. In particular, this fixes
	// a problem where spurious retransmits in a long-running TCP connection to a service
	// IP could result in the connection being closed with the error "Connection reset by
	// peer"
	ginkgo.It("should resolve connrection reset issue #74839 [Slow]", func() {
		serverLabel := map[string]string{
			"app": "boom-server",
		}
		clientLabel := map[string]string{
			"app": "client",
		}

		serverPod := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:   "boom-server",
				Labels: serverLabel,
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:  "boom-server",
						Image: imageutils.GetE2EImage(imageutils.RegressionIssue74839),
						Ports: []v1.ContainerPort{
							{
								ContainerPort: 9000, // Default port exposed by boom-server
							},
						},
					},
				},
				Affinity: &v1.Affinity{
					PodAntiAffinity: &v1.PodAntiAffinity{
						RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
							{
								LabelSelector: &metav1.LabelSelector{
									MatchLabels: clientLabel,
								},
								TopologyKey: "kubernetes.io/hostname",
							},
						},
					},
				},
			},
		}
		_, err := fr.ClientSet.CoreV1().Pods(fr.Namespace.Name).Create(serverPod)
		framework.ExpectNoError(err)

		err = e2epod.WaitForPodsRunningReady(fr.ClientSet, fr.Namespace.Name, 1, 0, framework.PodReadyBeforeTimeout, map[string]string{})
		framework.ExpectNoError(err)

		ginkgo.By("Server pod created")

		svc := &v1.Service{
			ObjectMeta: metav1.ObjectMeta{
				Name: "boom-server",
			},
			Spec: v1.ServiceSpec{
				Selector: serverLabel,
				Ports: []v1.ServicePort{
					{
						Protocol: v1.ProtocolTCP,
						Port:     9000,
					},
				},
			},
		}
		_, err = fr.ClientSet.CoreV1().Services(fr.Namespace.Name).Create(svc)
		framework.ExpectNoError(err)

		ginkgo.By("Server service created")

		pod := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:   "startup-script",
				Labels: clientLabel,
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:  "startup-script",
						Image: imageutils.GetE2EImage(imageutils.StartupScript),
						Command: []string{
							"bash", "-c", "while true; do sleep 2; nc boom-server 9000& done",
						},
					},
				},
				Affinity: &v1.Affinity{
					PodAntiAffinity: &v1.PodAntiAffinity{
						RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
							{
								LabelSelector: &metav1.LabelSelector{
									MatchLabels: serverLabel,
								},
								TopologyKey: "kubernetes.io/hostname",
							},
						},
					},
				},
				RestartPolicy: v1.RestartPolicyNever,
			},
		}
		_, err = fr.ClientSet.CoreV1().Pods(fr.Namespace.Name).Create(pod)
		framework.ExpectNoError(err)

		ginkgo.By("Client pod created")

		for i := 0; i < 20; i++ {
			time.Sleep(3 * time.Second)
			resultPod, err := fr.ClientSet.CoreV1().Pods(fr.Namespace.Name).Get(serverPod.Name, metav1.GetOptions{})
			framework.ExpectNoError(err)
			gomega.Expect(resultPod.Status.ContainerStatuses[0].LastTerminationState.Terminated).Should(gomega.BeNil())
		}
	})
})
<commit_msg>fix flakes on e2e test TCP CLOSE_WAIT timeout<commit_after>/*
Copyright 2016 The Kubernetes 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 network

import (
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"

	"k8s.io/kubernetes/test/e2e/framework"
	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
	"k8s.io/kubernetes/test/images/agnhost/net/nat"
	imageutils "k8s.io/kubernetes/test/utils/image"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
)

var kubeProxyE2eImage = imageutils.GetE2EImage(imageutils.Agnhost)

var _ = SIGDescribe("Network", func() {
	const (
		testDaemonHTTPPort     = 11301
		testDaemonTCPPort      = 11302
		deadlineTimeoutSeconds = 10
		postFinTimeoutSeconds  = 30
	)

	fr := framework.NewDefaultFramework("network")

	ginkgo.It("should set TCP CLOSE_WAIT timeout", func() {
		nodes, err := e2enode.GetBoundedReadySchedulableNodes(fr.ClientSet, 2)
		framework.ExpectNoError(err)
		if len(nodes.Items) < 2 {
			framework.Skipf(
				"Test requires >= 2 Ready nodes, but there are only %v nodes",
				len(nodes.Items))
		}

		ips := e2enode.CollectAddresses(nodes, v1.NodeInternalIP)

		type NodeInfo struct {
			node   *v1.Node
			name   string
			nodeIP string
		}

		clientNodeInfo := NodeInfo{
			node:   &nodes.Items[0],
			name:   nodes.Items[0].Name,
			nodeIP: ips[0],
		}

		serverNodeInfo := NodeInfo{
			node:   &nodes.Items[1],
			name:   nodes.Items[1].Name,
			nodeIP: ips[1],
		}

		zero := int64(0)

		// Create a pod to check the conntrack entries on the host node
		// It mounts the host /proc/net folder to be able to access
		// the nf_conntrack file with the host conntrack entries
		privileged := true

		hostExecPod := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "e2e-net-exec",
				Namespace: fr.Namespace.Name,
				Labels:    map[string]string{"app": "e2e-net-exec"},
			},
			Spec: v1.PodSpec{
				HostNetwork: true,
				NodeName:    clientNodeInfo.name,
				Containers: []v1.Container{
					{
						Name:            "e2e-net-exec",
						Image:           kubeProxyE2eImage,
						ImagePullPolicy: "Always",
						Args:            []string{"pause"},
						VolumeMounts: []v1.VolumeMount{
							{
								Name:      "proc-net",
								MountPath: "/rootfs/proc/net",
								ReadOnly:  true,
							},
						},
						SecurityContext: &v1.SecurityContext{
							Privileged: &privileged,
						},
					},
				},
				Volumes: []v1.Volume{
					{
						Name: "proc-net",
						VolumeSource: v1.VolumeSource{
							HostPath: &v1.HostPathVolumeSource{
								Path: "/proc/net",
							},
						},
					},
				},
				TerminationGracePeriodSeconds: &zero,
			},
		}
		fr.PodClient().CreateSync(hostExecPod)

		// Some distributions (Ubuntu 16.04 etc.) don't support the proc file.
		_, err = framework.RunHostCmd(fr.Namespace.Name, "e2e-net-exec",
			"ls /rootfs/proc/net/nf_conntrack")
		if err != nil && strings.Contains(err.Error(), "No such file or directory") {
			framework.Skipf("The node %s does not support /proc/net/nf_conntrack",
				clientNodeInfo.name)
		}
		framework.ExpectNoError(err)

		// Create the client and server pods
		clientPodSpec := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "e2e-net-client",
				Namespace: fr.Namespace.Name,
				Labels:    map[string]string{"app": "e2e-net-client"},
			},
			Spec: v1.PodSpec{
				NodeName: clientNodeInfo.name,
				Containers: []v1.Container{
					{
						Name:            "e2e-net-client",
						Image:           kubeProxyE2eImage,
						ImagePullPolicy: "Always",
						Args: []string{
							"net", "--serve", fmt.Sprintf(":%d", testDaemonHTTPPort),
						},
					},
				},
				TerminationGracePeriodSeconds: &zero,
			},
		}

		serverPodSpec := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "e2e-net-server",
				Namespace: fr.Namespace.Name,
				Labels:    map[string]string{"app": "e2e-net-server"},
			},
			Spec: v1.PodSpec{
				NodeName: serverNodeInfo.name,
				Containers: []v1.Container{
					{
						Name:            "e2e-net-server",
						Image:           kubeProxyE2eImage,
						ImagePullPolicy: "Always",
						Args: []string{
							"net",
							"--runner", "nat-closewait-server",
							"--options",
							fmt.Sprintf(`{"LocalAddr":":%v", "PostFinTimeoutSeconds":%v}`,
								testDaemonTCPPort,
								postFinTimeoutSeconds),
						},
						Ports: []v1.ContainerPort{
							{
								Name:          "tcp",
								ContainerPort: testDaemonTCPPort,
								HostPort:      testDaemonTCPPort,
							},
						},
					},
				},
				TerminationGracePeriodSeconds: &zero,
			},
		}

		ginkgo.By(fmt.Sprintf(
			"Launching a server daemon on node %v (node ip: %v, image: %v)",
			serverNodeInfo.name,
			serverNodeInfo.nodeIP,
			kubeProxyE2eImage))
		fr.PodClient().CreateSync(serverPodSpec)

		ginkgo.By(fmt.Sprintf(
			"Launching a client daemon on node %v (node ip: %v, image: %v)",
			clientNodeInfo.name,
			clientNodeInfo.nodeIP,
			kubeProxyE2eImage))
		fr.PodClient().CreateSync(clientPodSpec)

		ginkgo.By("Make client connect")

		options := nat.CloseWaitClientOptions{
			RemoteAddr: fmt.Sprintf("%v:%v",
				serverNodeInfo.nodeIP, testDaemonTCPPort),
			TimeoutSeconds:        deadlineTimeoutSeconds,
			PostFinTimeoutSeconds: postFinTimeoutSeconds,
			LeakConnection:        true,
		}

		jsonBytes, err := json.Marshal(options)
		framework.ExpectNoError(err, "could not marshal")

		cmd := fmt.Sprintf(
			`curl -X POST http://localhost:%v/run/nat-closewait-client -d `+
				`'%v' 2>/dev/null`,
			testDaemonHTTPPort,
			string(jsonBytes))
		// Run the closewait command in a subroutine so it keeps waiting during postFinTimeoutSeconds
		// otherwise the pod is deleted and the connection is closed loosing the conntrack entry
		go func() {
			framework.RunHostCmdOrDie(fr.Namespace.Name, "e2e-net-client", cmd)
		}()

		<-time.After(time.Duration(1) * time.Second)

		ginkgo.By("Checking /proc/net/nf_conntrack for the timeout")
		// These must be synchronized from the default values set in
		// pkg/apis/../defaults.go ConntrackTCPCloseWaitTimeout. The
		// current defaults are hidden in the initialization code.
		const epsilonSeconds = 60
		const expectedTimeoutSeconds = 60 * 60
		// Obtain the corresponding conntrack entry on the host checking
		// the nf_conntrack file from the pod e2e-net-exec.
		// It retries in a loop if the entry is not found.
		cmd = fmt.Sprintf("cat /rootfs/proc/net/nf_conntrack "+
			"| grep -m 1 'CLOSE_WAIT.*dst=%v.*dport=%v' ",
			serverNodeInfo.nodeIP,
			testDaemonTCPPort)
		if err := wait.PollImmediate(5*time.Second, 30*time.Second, func() (bool, error) {
			result, err := framework.RunHostCmd(fr.Namespace.Name, "e2e-net-exec", cmd)
			// retry if we can't obtain the conntrack entry
			if err != nil {
				framework.Logf("failed to obtain conntrack entry: %v %v", result, err)
				return false, nil
			}
			framework.Logf("conntrack entry for node %v and port %v:  %v", serverNodeInfo.nodeIP, testDaemonTCPPort, result)
			// Timeout in seconds is available as the fifth column of
			// the matched entry in /proc/net/nf_conntrack.
			line := strings.Fields(result)
			if len(line) < 5 {
				return false, fmt.Errorf("conntrack entry does not have a timeout field: %v", line)
			}
			timeoutSeconds, err := strconv.Atoi(line[4])
			if err != nil {
				return false, fmt.Errorf("failed to convert matched timeout %s to integer: %v", line[4], err)
			}
			if math.Abs(float64(timeoutSeconds-expectedTimeoutSeconds)) < epsilonSeconds {
				return true, nil
			}
			return false, fmt.Errorf("wrong TCP CLOSE_WAIT timeout: %v expected: %v", timeoutSeconds, expectedTimeoutSeconds)
		}); err != nil {
			framework.Failf("no conntrack entry for port %d on node %s", testDaemonTCPPort, serverNodeInfo.nodeIP)
		}
	})

	// Regression test for #74839, where:
	// Packets considered INVALID by conntrack are now dropped. In particular, this fixes
	// a problem where spurious retransmits in a long-running TCP connection to a service
	// IP could result in the connection being closed with the error "Connection reset by
	// peer"
	ginkgo.It("should resolve connrection reset issue #74839 [Slow]", func() {
		serverLabel := map[string]string{
			"app": "boom-server",
		}
		clientLabel := map[string]string{
			"app": "client",
		}

		serverPod := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:   "boom-server",
				Labels: serverLabel,
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:  "boom-server",
						Image: imageutils.GetE2EImage(imageutils.RegressionIssue74839),
						Ports: []v1.ContainerPort{
							{
								ContainerPort: 9000, // Default port exposed by boom-server
							},
						},
					},
				},
				Affinity: &v1.Affinity{
					PodAntiAffinity: &v1.PodAntiAffinity{
						RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
							{
								LabelSelector: &metav1.LabelSelector{
									MatchLabels: clientLabel,
								},
								TopologyKey: "kubernetes.io/hostname",
							},
						},
					},
				},
			},
		}
		_, err := fr.ClientSet.CoreV1().Pods(fr.Namespace.Name).Create(serverPod)
		framework.ExpectNoError(err)

		err = e2epod.WaitForPodsRunningReady(fr.ClientSet, fr.Namespace.Name, 1, 0, framework.PodReadyBeforeTimeout, map[string]string{})
		framework.ExpectNoError(err)

		ginkgo.By("Server pod created")

		svc := &v1.Service{
			ObjectMeta: metav1.ObjectMeta{
				Name: "boom-server",
			},
			Spec: v1.ServiceSpec{
				Selector: serverLabel,
				Ports: []v1.ServicePort{
					{
						Protocol: v1.ProtocolTCP,
						Port:     9000,
					},
				},
			},
		}
		_, err = fr.ClientSet.CoreV1().Services(fr.Namespace.Name).Create(svc)
		framework.ExpectNoError(err)

		ginkgo.By("Server service created")

		pod := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{
				Name:   "startup-script",
				Labels: clientLabel,
			},
			Spec: v1.PodSpec{
				Containers: []v1.Container{
					{
						Name:  "startup-script",
						Image: imageutils.GetE2EImage(imageutils.StartupScript),
						Command: []string{
							"bash", "-c", "while true; do sleep 2; nc boom-server 9000& done",
						},
					},
				},
				Affinity: &v1.Affinity{
					PodAntiAffinity: &v1.PodAntiAffinity{
						RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
							{
								LabelSelector: &metav1.LabelSelector{
									MatchLabels: serverLabel,
								},
								TopologyKey: "kubernetes.io/hostname",
							},
						},
					},
				},
				RestartPolicy: v1.RestartPolicyNever,
			},
		}
		_, err = fr.ClientSet.CoreV1().Pods(fr.Namespace.Name).Create(pod)
		framework.ExpectNoError(err)

		ginkgo.By("Client pod created")

		for i := 0; i < 20; i++ {
			time.Sleep(3 * time.Second)
			resultPod, err := fr.ClientSet.CoreV1().Pods(fr.Namespace.Name).Get(serverPod.Name, metav1.GetOptions{})
			framework.ExpectNoError(err)
			gomega.Expect(resultPod.Status.ContainerStatuses[0].LastTerminationState.Terminated).Should(gomega.BeNil())
		}
	})
})
<|endoftext|>


<commit_before>package images

import (
	"context"
	"fmt"
	"strings"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	e2e "k8s.io/kubernetes/test/e2e/framework"
	k8simage "k8s.io/kubernetes/test/utils/image"

	authorizationv1 "github.com/openshift/api/authorization/v1"

	exutil "github.com/openshift/origin/test/extended/util"
	"github.com/openshift/origin/test/extended/util/image"
)

var _ = g.Describe("[sig-imageregistry][Feature:Image] oc tag", func() {
	defer g.GinkgoRecover()
	oc := exutil.NewCLI("image-oc-tag")
	ctx := context.Background()

	g.It("should preserve image reference for external images", func() {
		var (
			externalImage = k8simage.GetE2EImage(k8simage.BusyBox)
			isName        = "busybox"
			isName2       = "busybox2"
		)

		externalRepository := externalImage
		if i := strings.LastIndex(externalRepository, ":"); i != -1 {
			externalRepository = externalRepository[:i]
		}

		g.By("import an external image")

		err := oc.Run("tag").Args("--source=docker", externalImage, isName+":latest").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = exutil.WaitForAnImageStreamTag(oc, oc.Namespace(), isName, "latest")
		o.Expect(err).NotTo(o.HaveOccurred())

		// check that the created image stream references the external registry
		is, err := oc.ImageClient().ImageV1().ImageStreams(oc.Namespace()).Get(ctx, isName, metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(is.Status.Tags).To(o.HaveLen(1))
		tag1 := is.Status.Tags[0]
		o.Expect(tag1.Tag).To(o.Equal("latest"))
		o.Expect(tag1.Items).To(o.HaveLen(1))
		o.Expect(tag1.Items[0].DockerImageReference).To(o.HavePrefix(externalRepository + "@"))

		g.By("copy the image to another image stream")

		err = oc.Run("tag").Args("--source=istag", isName+":latest", isName2+":latest").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = exutil.WaitForAnImageStreamTag(oc, oc.Namespace(), isName2, "latest")
		o.Expect(err).NotTo(o.HaveOccurred())

		// check that the new image stream references the still uses the external registry
		is, err = oc.ImageClient().ImageV1().ImageStreams(oc.Namespace()).Get(ctx, isName2, metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(is.Status.Tags).To(o.HaveLen(1))
		tag2 := is.Status.Tags[0]
		o.Expect(tag2.Tag).To(o.Equal("latest"))
		o.Expect(tag2.Items).To(o.HaveLen(1))
		o.Expect(tag2.Items[0].DockerImageReference).To(o.Equal(tag1.Items[0].DockerImageReference))
	})

	g.It("should change image reference for internal images", func() {
		var (
			isName     = "localimage"
			isName2    = "localimage2"
			dockerfile = fmt.Sprintf(`FROM %s
RUN touch /test-image
`, image.ShellImage())
		)

		g.By("determine the name of the integrated registry")

		registryHost, err := oc.Run("registry").Args("info").Output()
		o.Expect(err).NotTo(o.HaveOccurred())

		g.By("build an image")

		err = oc.Run("new-build").Args("-D", "-", "--to", isName+":latest").InputString(dockerfile).Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = exutil.WaitForABuild(oc.BuildClient().BuildV1().Builds(oc.Namespace()), isName+"-1", nil, nil, nil)
		o.Expect(err).NotTo(o.HaveOccurred())

		// check that the created image stream references the integrated registry
		is, err := oc.ImageClient().ImageV1().ImageStreams(oc.Namespace()).Get(ctx, isName, metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(is.Status.Tags).To(o.HaveLen(1))
		tag := is.Status.Tags[0]
		o.Expect(tag.Tag).To(o.Equal("latest"))
		o.Expect(tag.Items).To(o.HaveLen(1))
		o.Expect(tag.Items[0].DockerImageReference).To(o.HavePrefix(fmt.Sprintf("%s/%s/%s@", registryHost, oc.Namespace(), isName)))

		// extract the image digest
		ref := tag.Items[0].DockerImageReference
		digest := ref[strings.Index(ref, "@")+1:]
		o.Expect(digest).To(o.HavePrefix("sha256:"))

		g.By("copy the image to another image stream")

		err = oc.Run("tag").Args("--source=istag", isName+":latest", isName2+":latest").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = exutil.WaitForAnImageStreamTag(oc, oc.Namespace(), isName2, "latest")
		o.Expect(err).NotTo(o.HaveOccurred())

		// check that the new image stream uses its own name in the image reference
		is, err = oc.ImageClient().ImageV1().ImageStreams(oc.Namespace()).Get(ctx, isName2, metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(is.Status.Tags).To(o.HaveLen(1))
		tag = is.Status.Tags[0]
		o.Expect(tag.Tag).To(o.Equal("latest"))
		o.Expect(tag.Items).To(o.HaveLen(1))
		o.Expect(tag.Items[0].DockerImageReference).To(o.Equal(fmt.Sprintf("%s/%s/%s@%s", registryHost, oc.Namespace(), isName2, digest)))
	})

	g.It("should work when only imagestreams api is available", func() {
		err := oc.Run("tag").Args("--source=docker", image.ShellImage(), "testis:latest").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = exutil.WaitForAnImageStreamTag(oc, oc.Namespace(), "testis", "latest")
		o.Expect(err).NotTo(o.HaveOccurred())

		err = oc.Run("create").Args("serviceaccount", "testsa").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		e2e.Logf("Creating a role that allows to work with imagestreams, but not imagestreamtags...")

		_, err = oc.AdminAuthorizationClient().AuthorizationV1().Roles(oc.Namespace()).Create(ctx, &authorizationv1.Role{
			ObjectMeta: metav1.ObjectMeta{
				Name: "testrole",
			},
			Rules: []authorizationv1.PolicyRule{
				{
					Verbs:     []string{"get", "update"},
					APIGroups: []string{"image.openshift.io"},
					Resources: []string{"imagestreams"},
				},
			},
		}, metav1.CreateOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())

		err = oc.Run("policy").Args("add-role-to-user", "testrole", "-z", "testsa", "--role-namespace="+oc.Namespace()).Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		token, err := oc.Run("serviceaccounts").Args("get-token", "testsa").Output()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = oc.Run("login").Args("--token=" + token).Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = oc.Run("whoami").Args().Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = oc.Run("tag").Args("testis:latest", "testis:copy").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		e2e.Logf("Checking that the imagestream is updated...")

		is, err := oc.ImageClient().ImageV1().ImageStreams(oc.Namespace()).Get(ctx, "testis", metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())

		var tags []string
		for _, t := range is.Spec.Tags {
			tags = append(tags, t.Name)
		}
		o.Expect(tags).To(o.ContainElement("copy"), "testis spec.tags should contain the tag copy")
	})
})
<commit_msg>[test/oc_tag] Add internal option for registry info<commit_after>package images

import (
	"context"
	"fmt"
	"strings"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	e2e "k8s.io/kubernetes/test/e2e/framework"
	k8simage "k8s.io/kubernetes/test/utils/image"

	authorizationv1 "github.com/openshift/api/authorization/v1"

	exutil "github.com/openshift/origin/test/extended/util"
	"github.com/openshift/origin/test/extended/util/image"
)

var _ = g.Describe("[sig-imageregistry][Feature:Image] oc tag", func() {
	defer g.GinkgoRecover()
	oc := exutil.NewCLI("image-oc-tag")
	ctx := context.Background()

	g.It("should preserve image reference for external images", func() {
		var (
			externalImage = k8simage.GetE2EImage(k8simage.BusyBox)
			isName        = "busybox"
			isName2       = "busybox2"
		)

		externalRepository := externalImage
		if i := strings.LastIndex(externalRepository, ":"); i != -1 {
			externalRepository = externalRepository[:i]
		}

		g.By("import an external image")

		err := oc.Run("tag").Args("--source=docker", externalImage, isName+":latest").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = exutil.WaitForAnImageStreamTag(oc, oc.Namespace(), isName, "latest")
		o.Expect(err).NotTo(o.HaveOccurred())

		// check that the created image stream references the external registry
		is, err := oc.ImageClient().ImageV1().ImageStreams(oc.Namespace()).Get(ctx, isName, metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(is.Status.Tags).To(o.HaveLen(1))
		tag1 := is.Status.Tags[0]
		o.Expect(tag1.Tag).To(o.Equal("latest"))
		o.Expect(tag1.Items).To(o.HaveLen(1))
		o.Expect(tag1.Items[0].DockerImageReference).To(o.HavePrefix(externalRepository + "@"))

		g.By("copy the image to another image stream")

		err = oc.Run("tag").Args("--source=istag", isName+":latest", isName2+":latest").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = exutil.WaitForAnImageStreamTag(oc, oc.Namespace(), isName2, "latest")
		o.Expect(err).NotTo(o.HaveOccurred())

		// check that the new image stream references the still uses the external registry
		is, err = oc.ImageClient().ImageV1().ImageStreams(oc.Namespace()).Get(ctx, isName2, metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(is.Status.Tags).To(o.HaveLen(1))
		tag2 := is.Status.Tags[0]
		o.Expect(tag2.Tag).To(o.Equal("latest"))
		o.Expect(tag2.Items).To(o.HaveLen(1))
		o.Expect(tag2.Items[0].DockerImageReference).To(o.Equal(tag1.Items[0].DockerImageReference))
	})

	g.It("should change image reference for internal images", func() {
		var (
			isName     = "localimage"
			isName2    = "localimage2"
			dockerfile = fmt.Sprintf(`FROM %s
RUN touch /test-image
`, image.ShellImage())
		)

		g.By("determine the name of the integrated registry")

		registryHost, err := oc.Run("registry").Args("info", "--internal").Output()
		o.Expect(err).NotTo(o.HaveOccurred())

		g.By("build an image")

		err = oc.Run("new-build").Args("-D", "-", "--to", isName+":latest").InputString(dockerfile).Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = exutil.WaitForABuild(oc.BuildClient().BuildV1().Builds(oc.Namespace()), isName+"-1", nil, nil, nil)
		o.Expect(err).NotTo(o.HaveOccurred())

		// check that the created image stream references the integrated registry
		is, err := oc.ImageClient().ImageV1().ImageStreams(oc.Namespace()).Get(ctx, isName, metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(is.Status.Tags).To(o.HaveLen(1))
		tag := is.Status.Tags[0]
		o.Expect(tag.Tag).To(o.Equal("latest"))
		o.Expect(tag.Items).To(o.HaveLen(1))
		o.Expect(tag.Items[0].DockerImageReference).To(o.HavePrefix(fmt.Sprintf("%s/%s/%s@", registryHost, oc.Namespace(), isName)))

		// extract the image digest
		ref := tag.Items[0].DockerImageReference
		digest := ref[strings.Index(ref, "@")+1:]
		o.Expect(digest).To(o.HavePrefix("sha256:"))

		g.By("copy the image to another image stream")

		err = oc.Run("tag").Args("--source=istag", isName+":latest", isName2+":latest").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = exutil.WaitForAnImageStreamTag(oc, oc.Namespace(), isName2, "latest")
		o.Expect(err).NotTo(o.HaveOccurred())

		// check that the new image stream uses its own name in the image reference
		is, err = oc.ImageClient().ImageV1().ImageStreams(oc.Namespace()).Get(ctx, isName2, metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())
		o.Expect(is.Status.Tags).To(o.HaveLen(1))
		tag = is.Status.Tags[0]
		o.Expect(tag.Tag).To(o.Equal("latest"))
		o.Expect(tag.Items).To(o.HaveLen(1))
		o.Expect(tag.Items[0].DockerImageReference).To(o.Equal(fmt.Sprintf("%s/%s/%s@%s", registryHost, oc.Namespace(), isName2, digest)))
	})

	g.It("should work when only imagestreams api is available", func() {
		err := oc.Run("tag").Args("--source=docker", image.ShellImage(), "testis:latest").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = exutil.WaitForAnImageStreamTag(oc, oc.Namespace(), "testis", "latest")
		o.Expect(err).NotTo(o.HaveOccurred())

		err = oc.Run("create").Args("serviceaccount", "testsa").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		e2e.Logf("Creating a role that allows to work with imagestreams, but not imagestreamtags...")

		_, err = oc.AdminAuthorizationClient().AuthorizationV1().Roles(oc.Namespace()).Create(ctx, &authorizationv1.Role{
			ObjectMeta: metav1.ObjectMeta{
				Name: "testrole",
			},
			Rules: []authorizationv1.PolicyRule{
				{
					Verbs:     []string{"get", "update"},
					APIGroups: []string{"image.openshift.io"},
					Resources: []string{"imagestreams"},
				},
			},
		}, metav1.CreateOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())

		err = oc.Run("policy").Args("add-role-to-user", "testrole", "-z", "testsa", "--role-namespace="+oc.Namespace()).Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		token, err := oc.Run("serviceaccounts").Args("get-token", "testsa").Output()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = oc.Run("login").Args("--token=" + token).Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = oc.Run("whoami").Args().Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		err = oc.Run("tag").Args("testis:latest", "testis:copy").Execute()
		o.Expect(err).NotTo(o.HaveOccurred())

		e2e.Logf("Checking that the imagestream is updated...")

		is, err := oc.ImageClient().ImageV1().ImageStreams(oc.Namespace()).Get(ctx, "testis", metav1.GetOptions{})
		o.Expect(err).NotTo(o.HaveOccurred())

		var tags []string
		for _, t := range is.Spec.Tags {
			tags = append(tags, t.Name)
		}
		o.Expect(tags).To(o.ContainElement("copy"), "testis spec.tags should contain the tag copy")
	})
})
<|endoftext|>


<commit_before>// gobar
// Copyright (C) 2014 Karol 'Kenji Takahashi' Woźniak
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package main

import (
	"bufio"
	"io"
	"log"
	"regexp"
	"strconv"

	"github.com/BurntSushi/xgbutil/xgraphics"
)

// Align defines text piece alignment on the screen.
type Align uint8

const (
	LEFT Align = iota
	RIGHT
)

// Type EndScan is an artifical Error.
// Raised when parser should stop scanning.
type EndScan struct{}

func (e EndScan) Error() string { return "EndScan" }

// NewBGRA returns a new color definition in X compatible format.
// Input should be a hexagonal representation with alpha, i.e 0xAARRGGBB.
func NewBGRA(color uint64) *xgraphics.BGRA {
	a := uint8(color >> 24)
	r := uint8((color & 0x00ff0000) >> 16)
	g := uint8((color & 0x0000ff00) >> 8)
	b := uint8(color & 0x000000ff)
	return &xgraphics.BGRA{B: b, G: g, R: r, A: a}
}

// TextPiece stores formatting information for a text
// within single pair of brackets.
type TextPiece struct {
	Text       string
	Font       uint
	Align      Align
	Foreground *xgraphics.BGRA
	Background *xgraphics.BGRA
	Screens    []uint
	NotScreens []uint

	Origin *TextPiece
}

// TextParser is used to create a set of TextPieces from a textual definition.
type TextParser struct {
	rgbPattern *regexp.Regexp
}

// NewTextParser creates TextParser instance with
// correct necessary regexp definitions.
func NewTextParser() *TextParser {
	return &TextParser{regexp.MustCompile(`^0[xX][0-9a-fA-F]{8}$`)}
}

// Tokenize turns textual definition into a series of valid tokens.
// If no valid token is found at given place, char at 0 position is returned.
func (self *TextParser) Tokenize(
	data []byte, EOF bool,
) (advance int, token []byte, err error) {
	switch {
	case data[0] == '\n':
		err = EndScan{}
	case len(data) < 2:
		advance, token, err = 1, data[:1], nil
	case string(data[:2]) == "{F":
		advance, token, err = 2, data[:2], nil
	case string(data[:2]) == "{S":
		advance, token, err = 2, data[:2], nil
	case len(data) < 3:
		advance, token, err = 1, data[:1], nil
	case string(data[:3]) == "{CF":
		advance, token, err = 3, data[:3], nil
	case string(data[:3]) == "{CB":
		advance, token, err = 3, data[:3], nil
	case string(data[:3]) == "{AR":
		advance, token, err = 3, data[:3], nil
	case len(data) >= 10 && self.rgbPattern.Match(data[:10]):
		advance, token, err = 10, data[:10], nil
	case ('0' <= data[0] && data[0] <= '9') || data[0] == '-':
		i := 0
		if data[0] == '-' {
			i = 1
		}
		for _, n := range data[i:] {
			if !('0' <= n && n <= '9') {
				break
			}
			i += 1
		}
		advance, token, err = i, data[:i], nil
	default: // Also contains '}' and ','
		// TODO: Parsing whole text piece here, instead of returning
		// char-by-char, should perform better
		advance, token, err = 1, data[:1], nil
	}
	return
}

// Scan scans textual definition and returns array of TextPieces.
// Possible empty pieces are omitted in the returned array.
func (self *TextParser) Scan(r io.Reader) []*TextPiece {
	var text []*TextPiece

	scanner := bufio.NewScanner(r)

	scanner.Split(self.Tokenize)

	currentText := &TextPiece{}
	text = append(text, currentText)

	currentIndex := func() int {
		for i, t := range text {
			if t == currentText {
				return i
			}
		}
		return 0
	}

	moveCurrent := func(end bool) *TextPiece {
		newCurrent := &TextPiece{}
		if end {
			*newCurrent = *currentText.Origin
		} else {
			*newCurrent = *currentText
			newCurrent.Origin = currentText
		}
		newCurrent.Text = ""
		if currentText.Align == RIGHT {
			i := currentIndex()
			text = append(text, &TextPiece{})
			copy(text[i+1:], text[i:])
			text[i] = newCurrent
		} else {
			text = append(text, newCurrent)
		}
		currentText = newCurrent
		return newCurrent
	}

	logPieceError := func(err error, pieces ...string) {
		log.Printf("Parsing `%q`: %s", pieces, err)
		log.Print(err)
		for _, piece := range pieces {
			currentText.Text += piece
		}
	}

	screening := false
	escaping := false
	bracketing := 0
	for scanner.Scan() {
		stext := scanner.Text()
		switch {
		case stext == "\\":
			escaping = true
			continue
		case !escaping && stext == "{F":
			scanner.Scan()
			text := scanner.Text()
			font, err := strconv.Atoi(text)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(false)
			newCurrent.Font = uint(font)
		case !escaping && stext == "{S":
			scanner.Scan()
			text := scanner.Text()
			screen, err := strconv.Atoi(text)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(false)
			if text[0] == '-' {
				newCurrent.NotScreens = append(newCurrent.NotScreens, uint(-screen))
			} else {
				newCurrent.Screens = append(newCurrent.Screens, uint(screen))
			}
			screening = true
		case !escaping && stext == "{CF":
			scanner.Scan()
			text := scanner.Text()
			fg, err := strconv.ParseUint(text, 0, 32)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(false)
			newCurrent.Foreground = NewBGRA(fg)
		case !escaping && stext == "{CB":
			scanner.Scan()
			text := scanner.Text()
			bg, err := strconv.ParseUint(text, 0, 32)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(false)
			newCurrent.Background = NewBGRA(bg)
		case !escaping && stext == "{AR":
			newCurrent := moveCurrent(false)
			newCurrent.Align = RIGHT
		case !escaping && stext == "{":
			bracketing += 1
		case !escaping && stext == "}":
			if bracketing > 0 {
				bracketing -= 1
				continue
			}
			screening = false
			if currentText.Origin != nil {
				moveCurrent(true)
				continue
			}
			fallthrough
		default:
			if screening && stext == "," {
				scanner.Scan()
				text := scanner.Text()
				screen, err := strconv.Atoi(text)
				if err != nil {
					logPieceError(err, stext, text)
				}
				currentText.Screens = append(currentText.Screens, uint(screen))
			} else {
				currentText.Text += stext
			}
			escaping = false
		}
	}

	//Remove possible empty pieces.
	var text2 []*TextPiece
	for _, piece := range text {
		if piece.Text != "" {
			text2 = append(text2, piece)
		}
	}

	return text2
}
<commit_msg>one log is enough<commit_after>// gobar
// Copyright (C) 2014 Karol 'Kenji Takahashi' Woźniak
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package main

import (
	"bufio"
	"io"
	"log"
	"regexp"
	"strconv"

	"github.com/BurntSushi/xgbutil/xgraphics"
)

// Align defines text piece alignment on the screen.
type Align uint8

const (
	LEFT Align = iota
	RIGHT
)

// Type EndScan is an artifical Error.
// Raised when parser should stop scanning.
type EndScan struct{}

func (e EndScan) Error() string { return "EndScan" }

// NewBGRA returns a new color definition in X compatible format.
// Input should be a hexagonal representation with alpha, i.e 0xAARRGGBB.
func NewBGRA(color uint64) *xgraphics.BGRA {
	a := uint8(color >> 24)
	r := uint8((color & 0x00ff0000) >> 16)
	g := uint8((color & 0x0000ff00) >> 8)
	b := uint8(color & 0x000000ff)
	return &xgraphics.BGRA{B: b, G: g, R: r, A: a}
}

// TextPiece stores formatting information for a text
// within single pair of brackets.
type TextPiece struct {
	Text       string
	Font       uint
	Align      Align
	Foreground *xgraphics.BGRA
	Background *xgraphics.BGRA
	Screens    []uint
	NotScreens []uint

	Origin *TextPiece
}

// TextParser is used to create a set of TextPieces from a textual definition.
type TextParser struct {
	rgbPattern *regexp.Regexp
}

// NewTextParser creates TextParser instance with
// correct necessary regexp definitions.
func NewTextParser() *TextParser {
	return &TextParser{regexp.MustCompile(`^0[xX][0-9a-fA-F]{8}$`)}
}

// Tokenize turns textual definition into a series of valid tokens.
// If no valid token is found at given place, char at 0 position is returned.
func (self *TextParser) Tokenize(
	data []byte, EOF bool,
) (advance int, token []byte, err error) {
	switch {
	case data[0] == '\n':
		err = EndScan{}
	case len(data) < 2:
		advance, token, err = 1, data[:1], nil
	case string(data[:2]) == "{F":
		advance, token, err = 2, data[:2], nil
	case string(data[:2]) == "{S":
		advance, token, err = 2, data[:2], nil
	case len(data) < 3:
		advance, token, err = 1, data[:1], nil
	case string(data[:3]) == "{CF":
		advance, token, err = 3, data[:3], nil
	case string(data[:3]) == "{CB":
		advance, token, err = 3, data[:3], nil
	case string(data[:3]) == "{AR":
		advance, token, err = 3, data[:3], nil
	case len(data) >= 10 && self.rgbPattern.Match(data[:10]):
		advance, token, err = 10, data[:10], nil
	case ('0' <= data[0] && data[0] <= '9') || data[0] == '-':
		i := 0
		if data[0] == '-' {
			i = 1
		}
		for _, n := range data[i:] {
			if !('0' <= n && n <= '9') {
				break
			}
			i += 1
		}
		advance, token, err = i, data[:i], nil
	default: // Also contains '}' and ','
		// TODO: Parsing whole text piece here, instead of returning
		// char-by-char, should perform better
		advance, token, err = 1, data[:1], nil
	}
	return
}

// Scan scans textual definition and returns array of TextPieces.
// Possible empty pieces are omitted in the returned array.
func (self *TextParser) Scan(r io.Reader) []*TextPiece {
	var text []*TextPiece

	scanner := bufio.NewScanner(r)

	scanner.Split(self.Tokenize)

	currentText := &TextPiece{}
	text = append(text, currentText)

	currentIndex := func() int {
		for i, t := range text {
			if t == currentText {
				return i
			}
		}
		return 0
	}

	moveCurrent := func(end bool) *TextPiece {
		newCurrent := &TextPiece{}
		if end {
			*newCurrent = *currentText.Origin
		} else {
			*newCurrent = *currentText
			newCurrent.Origin = currentText
		}
		newCurrent.Text = ""
		if currentText.Align == RIGHT {
			i := currentIndex()
			text = append(text, &TextPiece{})
			copy(text[i+1:], text[i:])
			text[i] = newCurrent
		} else {
			text = append(text, newCurrent)
		}
		currentText = newCurrent
		return newCurrent
	}

	logPieceError := func(err error, pieces ...string) {
		log.Printf("Problem parsing `%q`: %s", pieces, err)
		for _, piece := range pieces {
			currentText.Text += piece
		}
	}

	screening := false
	escaping := false
	bracketing := 0
	for scanner.Scan() {
		stext := scanner.Text()
		switch {
		case stext == "\\":
			escaping = true
			continue
		case !escaping && stext == "{F":
			scanner.Scan()
			text := scanner.Text()
			font, err := strconv.Atoi(text)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(false)
			newCurrent.Font = uint(font)
		case !escaping && stext == "{S":
			scanner.Scan()
			text := scanner.Text()
			screen, err := strconv.Atoi(text)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(false)
			if text[0] == '-' {
				newCurrent.NotScreens = append(newCurrent.NotScreens, uint(-screen))
			} else {
				newCurrent.Screens = append(newCurrent.Screens, uint(screen))
			}
			screening = true
		case !escaping && stext == "{CF":
			scanner.Scan()
			text := scanner.Text()
			fg, err := strconv.ParseUint(text, 0, 32)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(false)
			newCurrent.Foreground = NewBGRA(fg)
		case !escaping && stext == "{CB":
			scanner.Scan()
			text := scanner.Text()
			bg, err := strconv.ParseUint(text, 0, 32)
			if err != nil {
				logPieceError(err, stext, text)
			}
			newCurrent := moveCurrent(false)
			newCurrent.Background = NewBGRA(bg)
		case !escaping && stext == "{AR":
			newCurrent := moveCurrent(false)
			newCurrent.Align = RIGHT
		case !escaping && stext == "{":
			bracketing += 1
		case !escaping && stext == "}":
			if bracketing > 0 {
				bracketing -= 1
				continue
			}
			screening = false
			if currentText.Origin != nil {
				moveCurrent(true)
				continue
			}
			fallthrough
		default:
			if screening && stext == "," {
				scanner.Scan()
				text := scanner.Text()
				screen, err := strconv.Atoi(text)
				if err != nil {
					logPieceError(err, stext, text)
				}
				currentText.Screens = append(currentText.Screens, uint(screen))
			} else {
				currentText.Text += stext
			}
			escaping = false
		}
	}

	//Remove possible empty pieces.
	var text2 []*TextPiece
	for _, piece := range text {
		if piece.Text != "" {
			text2 = append(text2, piece)
		}
	}

	return text2
}
<|endoftext|>


<commit_before>package bot

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

/*

Job builtins are special:
- They're available in every channel
- Permissions are checked against the job being operated on, not job builtin

*/

const histPageSize = 2048    // how much history to display at a time
const maxMailBody = 10485760 // 10MB

func init() {
	RegisterPlugin("builtin-history", PluginHandler{Handler: jobhistory})
	RegisterPlugin("builtin-jobcmd", PluginHandler{Handler: jobcommands})
}

func jobcommands(r *Robot, command string, args ...string) (retval TaskRetVal) {
	if command == "init" {
		return
	}
	switch command {
	case "jobs":
		var jl []string
		alljobs := len(args[0]) > 0
		if alljobs {
			jl = []string{"Here's a list of all the jobs I know about:"}
		} else {
			jl = []string{"Here's a list of jobs for this channel:"}
		}
		c := r.getContext()
		for _, t := range c.tasks.t {
			if !r.jobVisible(t, alljobs, true) {
				continue
			}
			task, _, _ := getTask(t)
			after := ""
			if task.Disabled {
				after = fmt.Sprintf(" (disabled: %s)", task.reason)
			}
			if alljobs && r.Channel != task.Channel {
				jl = append(jl, fmt.Sprintf("%s (channel: %s)%s", task.name, task.Channel, after))
			} else {
				jl = append(jl, fmt.Sprintf("%s%s", task.name, after))
			}
		}
		if len(jl) == 1 {
			if alljobs {
				r.Say("I dont' have any jobs configured")
				return
			}
			r.Say("I don't see any jobs configured for this channel")
			return
		}
		r.Say(strings.Join(jl, "\n"))
	}
	return
}

func emailhistory(r *Robot, hp HistoryProvider, user, address, spec string, run int) (retval TaskRetVal) {
	f, err := hp.GetHistory(spec, run)
	if err != nil {
		Log(Error, fmt.Sprintf("Error getting history %d for task '%s': %v", run, spec, err))
		r.Say(fmt.Sprintf("History %d for '%s' not available", run, spec))
		return
	}
	lr := io.LimitReader(f, maxMailBody)
	body := new(bytes.Buffer)
	body.Write([]byte("
\n"))
	b, rerr := ioutil.ReadAll(lr)
	if rerr != nil {
		r.Log(Error, fmt.Sprintf("reading history #%d for '%s': %v", run, spec, rerr))
		r.Reply("There was a problem reading the history, check with an administrator")
		return
	}
	body.Write(b)
	body.Write([]byte("\n
"))
	subject := fmt.Sprintf("History for '%s', run %d", spec, run)
	var ret RetVal
	if len(user) > 0 {
		ret = r.EmailUser(user, subject, body, true)
	} else if len(address) > 0 {
		ret = r.EmailAddress(address, subject, body, true)
	} else {
		ret = r.Email(subject, body, true)
	}
	if ret != Ok {
		r.Reply("There was a problem emailing the history log, contact an administrator")
		return
	}
	r.Say("Email sent")
	return
}

func pagehistory(r *Robot, hp HistoryProvider, spec string, run int) (retval TaskRetVal) {
	f, err := hp.GetHistory(spec, run)
	if err != nil {
		Log(Error, fmt.Sprintf("Error getting history %d for task '%s': %v", run, spec, err))
		r.Say(fmt.Sprintf("History %d for '%s' not available", run, spec))
		return
	}
	var line string
	scanner := bufio.NewScanner(f)
	finished := false
PageLoop:
	for {
		size := 0
		lines := make([]string, 0, 40)
		if len(line) > 0 {
			lines = append(lines, line)
			size += len(line) + 1
			line = ""
		}
		for size < histPageSize {
			if scanner.Scan() {
				line = scanner.Text()
				size += len(line) + 1
				if size < histPageSize {
					lines = append(lines, line)
					line = ""
				}
			} else {
				finished = true
				break
			}
		}
		r.Fixed().Say(strings.Join(lines, "\n"))
		if finished {
			break
		}
		rep, ret := r.PromptForReply("paging", "'c' to continue, 'q' to quit, or 'n' to skip to the next section")
		if ret != Ok {
			r.Say("(quitting)")
			break PageLoop
		} else {
		ContinueSwitch:
			switch rep {
			case "q", "Q":
				r.Say("(ok, quitting)")
				break PageLoop
			case "n", "N":
				for scanner.Scan() {
					line = scanner.Text()
					if strings.HasPrefix(line, "***") {
						break ContinueSwitch
					}
				}
			}
		}
	}
	return
}

func jobhistory(r *Robot, command string, args ...string) (retval TaskRetVal) {
	if command == "init" {
		return
	}

	var histType, latest, histSpec, index, user, address string

	switch command {
	case "history":
		histType = args[0]
		latest = args[1]
		histSpec = args[2]
		index = args[3]
	case "mailhistory":
		histType = "email"
		latest = args[0]
		histSpec = args[1]
		index = args[2]
		user = args[3]
		address = args[4]
	}

	// boilerplate availability and security checking for job commands
	c := r.getContext()
	jobName := strings.Split(histSpec, ":")[0]
	t := c.jobAvailable(jobName)
	if t == nil {
		return
	}
	if !c.jobSecurityCheck(t, command) {
		return
	}
	vr := r.MessageFormat(Variable)

	switch command {
	case "history", "mailhistory":
		botCfg.RLock()
		hp := botCfg.history
		if hp == nil {
			botCfg.RUnlock()
			r.Reply("No history provider configured")
			return
		}
		botCfg.RUnlock()
		var jh jobHistory
		key := histPrefix + histSpec
		_, _, ret := checkoutDatum(key, &jh, false)
		if ret != Ok {
			r.Say(fmt.Sprintf("No history found for '%s'", histSpec))
			return
		}
		if len(latest) == 0 && len(index) == 0 {
			if len(jh.ExtendedNamespaces) > 0 {
				nsl := make([]string, len(jh.ExtendedNamespaces)+2)
				nsl = append(nsl, fmt.Sprintf("Namespaces for %s:", histSpec))
				if len(jh.Histories) > 0 {
					nsl = append(nsl, "0: (base job)")
				}
				for i, ens := range jh.ExtendedNamespaces {
					nsl = append(nsl, fmt.Sprintf("%d: %s", i+1, ens))
				}
				vr.Say(strings.Join(nsl, "\n"))
				rep, ret := r.PromptForReply("selection", "Which namespace #?")
				if ret != Ok {
					r.Say("(quitting history command)")
					return
				}
				if rep != "0" {
					i, _ := strconv.Atoi(rep)
					histSpec += ":" + jh.ExtendedNamespaces[i-1]
					key = histPrefix + histSpec
					_, _, ret = checkoutDatum(key, &jh, false)
				}
			}
		}
		if len(jh.Histories) == 0 {
			r.Say(fmt.Sprintf("No history found for '%s'", histSpec))
			return
		}

		// remember which job we're talking about
		ctx := memoryContext{"context:task", r.User, r.Channel}
		s := shortTermMemory{histSpec, time.Now()}
		shortTermMemories.Lock()
		shortTermMemories.m[ctx] = s
		shortTermMemories.Unlock()

		var idx int
		if len(latest) == 0 && len(index) == 0 {
			hl := make([]string, len(jh.Histories)+1)
			hl = append(hl, fmt.Sprintf("History of job runs for '%s':", histSpec))
			for _, he := range jh.Histories {
				hl = append(hl, fmt.Sprintf("Run %d - %s", he.LogIndex, he.CreateTime))
			}
			vr.Say(strings.Join(hl, "\n"))
			rep, ret := r.PromptForReply("selection", "Which run #?")
			if ret != Ok {
				r.Say("(quitting history command)")
				return
			}
			idx, _ = strconv.Atoi(rep)
		} else if len(latest) > 0 {
			idx = len(jh.Histories) - 1
		} else {
			idx, _ = strconv.Atoi(index)
		}
		switch histType {
		case "mail", "email":
			if len(user) > 0 {
				return emailhistory(r, hp, user, "", histSpec, idx)
			} else if len(address) > 0 {
				return emailhistory(r, hp, "", address, histSpec, idx)
			} else {
				return emailhistory(r, hp, "", "", histSpec, idx)
			}
		case "link":
			if link, ok := hp.GetHistoryURL(histSpec, idx); ok {
				r.Say(fmt.Sprintf("Here you go: %s", link))
				return
			}
			r.Say("No link available")
			return
		default:
			return pagehistory(r, hp, histSpec, idx)
		}
	}
	return
}

// jobSecurityCheck performs all security checks - RequireAdmin, Authorization
// and Elevation - and returns true if passed. It will message the user and
// return false if a check fails.
func (c *botContext) jobSecurityCheck(t interface{}, command string) bool {
	if c.automaticTask {
		return true
	}
	ct := c.currentTask
	task, _, _ := getTask(t)
	if task.RequireAdmin {
		r := c.makeRobot()
		if !r.CheckAdmin() {
			r.Say("Sorry, that command is only available to bot administrators")
			return false
		}
	}
	if c.checkAuthorization(t, command) != Success {
		return false
	}
	if !c.elevated {
		eret, required := c.checkElevation(t, command)
		if eret != Success {
			return false
		}
		if required {
			c.elevated = true
		}
	}
	// Restore currentTask, potentially modified by checkAuthorization/checkElevation
	c.currentTask = ct
	return true
}

// jobVisible checks whether a user should see a job in a channel, unless
// ignoreChannelRestrictions is set. Note that changes to logic in jobVisible
// may need to propagate to jobAvailable, below.
func (r *Robot) jobVisible(t interface{}, ignoreChannelRestrictions, disabledOk bool) bool {
	task, _, job := getTask(t)
	if job == nil {
		return false
	}
	if task.Disabled && !disabledOk {
		return false
	}
	if !ignoreChannelRestrictions && r.Channel != task.Channel {
		return false
	}
	if len(task.Users) > 0 {
		userOk := false
		for _, allowedUser := range task.Users {
			match, err := filepath.Match(allowedUser, r.User)
			if match && err == nil {
				userOk = true
			}
		}
		if !userOk {
			return false
		}
	}
	if task.RequireAdmin {
		isAdmin := false
		botCfg.RLock()
		admins := botCfg.adminUsers
		botCfg.RUnlock()
		for _, adminUser := range admins {
			if r.User == adminUser {
				isAdmin = true
				break
			}
		}
		if !isAdmin {
			return false
		}
	}
	return true
}

// jobAvailable does the work of looking up a job and checking whether it's
// available, and messaging the user if it's not. Only called for interactive
// job commands like history, run job, etc. where the user provides a job name.
// Note that changes to login in jobAvailable may need to propagate to
// jobVisible, above.
func (c *botContext) jobAvailable(taskName string) interface{} {
	r := c.makeRobot()
	t := c.tasks.getTaskByName(taskName)
	if t == nil {
		r.Say(fmt.Sprintf("Sorry, I don't have a task named '%s' configured", taskName))
		return nil
	}
	task, _, job := getTask(t)
	isJob := job != nil
	if !isJob {
		r.Say(fmt.Sprintf("Sorry, '%s' isn't a job", taskName))
		return nil
	}
	if c.automaticTask {
		return t
	}
	// If there's already a job initialized, this is a pipeline task for that
	// job, and should be available regardless of channel.
	if !c.jobInitialized && r.Channel != task.Channel {
		c.debugTask(task, fmt.Sprintf("not available in channel '%s'", task.Channel), false)
		r.Say(fmt.Sprintf("Sorry, job '%s' isn't available in this channel, try '%s'", taskName, task.Channel))
		return nil
	}
	if task.RequireAdmin {
		isAdmin := false
		botCfg.RLock()
		admins := botCfg.adminUsers
		botCfg.RUnlock()
		for _, adminUser := range admins {
			if r.User == adminUser {
				isAdmin = true
				break
			}
		}
		if !isAdmin {
			r.Say(fmt.Sprintf("Sorry, '%s' is only available to bot administrators", taskName))
			return nil
		}
	}
	if len(task.Users) > 0 {
		userOk := false
		for _, allowedUser := range task.Users {
			match, err := filepath.Match(allowedUser, r.User)
			if match && err == nil {
				userOk = true
			}
		}
		if !userOk {
			r.Say("Sorry, you're not on the list of allowed users for that job")
			c.debugTask(task, "user is not on the list of allowed users", false)
			return nil
		}
	}
	return t
}
<commit_msg>Better value for 'last' index<commit_after>package bot

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

/*

Job builtins are special:
- They're available in every channel
- Permissions are checked against the job being operated on, not job builtin

*/

const histPageSize = 2048    // how much history to display at a time
const maxMailBody = 10485760 // 10MB

func init() {
	RegisterPlugin("builtin-history", PluginHandler{Handler: jobhistory})
	RegisterPlugin("builtin-jobcmd", PluginHandler{Handler: jobcommands})
}

func jobcommands(r *Robot, command string, args ...string) (retval TaskRetVal) {
	if command == "init" {
		return
	}
	switch command {
	case "jobs":
		var jl []string
		alljobs := len(args[0]) > 0
		if alljobs {
			jl = []string{"Here's a list of all the jobs I know about:"}
		} else {
			jl = []string{"Here's a list of jobs for this channel:"}
		}
		c := r.getContext()
		for _, t := range c.tasks.t {
			if !r.jobVisible(t, alljobs, true) {
				continue
			}
			task, _, _ := getTask(t)
			after := ""
			if task.Disabled {
				after = fmt.Sprintf(" (disabled: %s)", task.reason)
			}
			if alljobs && r.Channel != task.Channel {
				jl = append(jl, fmt.Sprintf("%s (channel: %s)%s", task.name, task.Channel, after))
			} else {
				jl = append(jl, fmt.Sprintf("%s%s", task.name, after))
			}
		}
		if len(jl) == 1 {
			if alljobs {
				r.Say("I dont' have any jobs configured")
				return
			}
			r.Say("I don't see any jobs configured for this channel")
			return
		}
		r.Say(strings.Join(jl, "\n"))
	}
	return
}

func emailhistory(r *Robot, hp HistoryProvider, user, address, spec string, run int) (retval TaskRetVal) {
	f, err := hp.GetHistory(spec, run)
	if err != nil {
		Log(Error, fmt.Sprintf("Error getting history %d for task '%s': %v", run, spec, err))
		r.Say(fmt.Sprintf("History %d for '%s' not available", run, spec))
		return
	}
	lr := io.LimitReader(f, maxMailBody)
	body := new(bytes.Buffer)
	body.Write([]byte("
\n"))
	b, rerr := ioutil.ReadAll(lr)
	if rerr != nil {
		r.Log(Error, fmt.Sprintf("reading history #%d for '%s': %v", run, spec, rerr))
		r.Reply("There was a problem reading the history, check with an administrator")
		return
	}
	body.Write(b)
	body.Write([]byte("\n
"))
	subject := fmt.Sprintf("History for '%s', run %d", spec, run)
	var ret RetVal
	if len(user) > 0 {
		ret = r.EmailUser(user, subject, body, true)
	} else if len(address) > 0 {
		ret = r.EmailAddress(address, subject, body, true)
	} else {
		ret = r.Email(subject, body, true)
	}
	if ret != Ok {
		r.Reply("There was a problem emailing the history log, contact an administrator")
		return
	}
	r.Say("Email sent")
	return
}

func pagehistory(r *Robot, hp HistoryProvider, spec string, run int) (retval TaskRetVal) {
	f, err := hp.GetHistory(spec, run)
	if err != nil {
		Log(Error, fmt.Sprintf("Error getting history %d for task '%s': %v", run, spec, err))
		r.Say(fmt.Sprintf("History %d for '%s' not available", run, spec))
		return
	}
	var line string
	scanner := bufio.NewScanner(f)
	finished := false
PageLoop:
	for {
		size := 0
		lines := make([]string, 0, 40)
		if len(line) > 0 {
			lines = append(lines, line)
			size += len(line) + 1
			line = ""
		}
		for size < histPageSize {
			if scanner.Scan() {
				line = scanner.Text()
				size += len(line) + 1
				if size < histPageSize {
					lines = append(lines, line)
					line = ""
				}
			} else {
				finished = true
				break
			}
		}
		r.Fixed().Say(strings.Join(lines, "\n"))
		if finished {
			break
		}
		rep, ret := r.PromptForReply("paging", "'c' to continue, 'q' to quit, or 'n' to skip to the next section")
		if ret != Ok {
			r.Say("(quitting)")
			break PageLoop
		} else {
		ContinueSwitch:
			switch rep {
			case "q", "Q":
				r.Say("(ok, quitting)")
				break PageLoop
			case "n", "N":
				for scanner.Scan() {
					line = scanner.Text()
					if strings.HasPrefix(line, "***") {
						break ContinueSwitch
					}
				}
			}
		}
	}
	return
}

func jobhistory(r *Robot, command string, args ...string) (retval TaskRetVal) {
	if command == "init" {
		return
	}

	var histType, latest, histSpec, index, user, address string

	switch command {
	case "history":
		histType = args[0]
		latest = args[1]
		histSpec = args[2]
		index = args[3]
	case "mailhistory":
		histType = "email"
		latest = args[0]
		histSpec = args[1]
		index = args[2]
		user = args[3]
		address = args[4]
	}

	// boilerplate availability and security checking for job commands
	c := r.getContext()
	jobName := strings.Split(histSpec, ":")[0]
	t := c.jobAvailable(jobName)
	if t == nil {
		return
	}
	if !c.jobSecurityCheck(t, command) {
		return
	}
	vr := r.MessageFormat(Variable)

	switch command {
	case "history", "mailhistory":
		botCfg.RLock()
		hp := botCfg.history
		if hp == nil {
			botCfg.RUnlock()
			r.Reply("No history provider configured")
			return
		}
		botCfg.RUnlock()
		var jh jobHistory
		key := histPrefix + histSpec
		_, _, ret := checkoutDatum(key, &jh, false)
		if ret != Ok {
			r.Say(fmt.Sprintf("No history found for '%s'", histSpec))
			return
		}
		if len(latest) == 0 && len(index) == 0 {
			if len(jh.ExtendedNamespaces) > 0 {
				nsl := make([]string, len(jh.ExtendedNamespaces)+2)
				nsl = append(nsl, fmt.Sprintf("Namespaces for %s:", histSpec))
				if len(jh.Histories) > 0 {
					nsl = append(nsl, "0: (base job)")
				}
				for i, ens := range jh.ExtendedNamespaces {
					nsl = append(nsl, fmt.Sprintf("%d: %s", i+1, ens))
				}
				vr.Say(strings.Join(nsl, "\n"))
				rep, ret := r.PromptForReply("selection", "Which namespace #?")
				if ret != Ok {
					r.Say("(quitting history command)")
					return
				}
				if rep != "0" {
					i, _ := strconv.Atoi(rep)
					histSpec += ":" + jh.ExtendedNamespaces[i-1]
					key = histPrefix + histSpec
					_, _, ret = checkoutDatum(key, &jh, false)
				}
			}
		}
		if len(jh.Histories) == 0 {
			r.Say(fmt.Sprintf("No history found for '%s'", histSpec))
			return
		}

		// remember which job we're talking about
		ctx := memoryContext{"context:task", r.User, r.Channel}
		s := shortTermMemory{histSpec, time.Now()}
		shortTermMemories.Lock()
		shortTermMemories.m[ctx] = s
		shortTermMemories.Unlock()

		var idx int
		if len(latest) == 0 && len(index) == 0 {
			hl := make([]string, len(jh.Histories)+1)
			hl = append(hl, fmt.Sprintf("History of job runs for '%s':", histSpec))
			for _, he := range jh.Histories {
				hl = append(hl, fmt.Sprintf("Run %d - %s", he.LogIndex, he.CreateTime))
			}
			vr.Say(strings.Join(hl, "\n"))
			rep, ret := r.PromptForReply("selection", "Which run #?")
			if ret != Ok {
				r.Say("(quitting history command)")
				return
			}
			idx, _ = strconv.Atoi(rep)
		} else if len(latest) > 0 {
			idx = jh.NextIndex - 1
			if idx < 0 {
				idx = 0
			}
		} else {
			idx, _ = strconv.Atoi(index)
		}
		switch histType {
		case "mail", "email":
			if len(user) > 0 {
				return emailhistory(r, hp, user, "", histSpec, idx)
			} else if len(address) > 0 {
				return emailhistory(r, hp, "", address, histSpec, idx)
			} else {
				return emailhistory(r, hp, "", "", histSpec, idx)
			}
		case "link":
			if link, ok := hp.GetHistoryURL(histSpec, idx); ok {
				r.Say(fmt.Sprintf("Here you go: %s", link))
				return
			}
			r.Say("No link available")
			return
		default:
			return pagehistory(r, hp, histSpec, idx)
		}
	}
	return
}

// jobSecurityCheck performs all security checks - RequireAdmin, Authorization
// and Elevation - and returns true if passed. It will message the user and
// return false if a check fails.
func (c *botContext) jobSecurityCheck(t interface{}, command string) bool {
	if c.automaticTask {
		return true
	}
	ct := c.currentTask
	task, _, _ := getTask(t)
	if task.RequireAdmin {
		r := c.makeRobot()
		if !r.CheckAdmin() {
			r.Say("Sorry, that command is only available to bot administrators")
			return false
		}
	}
	if c.checkAuthorization(t, command) != Success {
		return false
	}
	if !c.elevated {
		eret, required := c.checkElevation(t, command)
		if eret != Success {
			return false
		}
		if required {
			c.elevated = true
		}
	}
	// Restore currentTask, potentially modified by checkAuthorization/checkElevation
	c.currentTask = ct
	return true
}

// jobVisible checks whether a user should see a job in a channel, unless
// ignoreChannelRestrictions is set. Note that changes to logic in jobVisible
// may need to propagate to jobAvailable, below.
func (r *Robot) jobVisible(t interface{}, ignoreChannelRestrictions, disabledOk bool) bool {
	task, _, job := getTask(t)
	if job == nil {
		return false
	}
	if task.Disabled && !disabledOk {
		return false
	}
	if !ignoreChannelRestrictions && r.Channel != task.Channel {
		return false
	}
	if len(task.Users) > 0 {
		userOk := false
		for _, allowedUser := range task.Users {
			match, err := filepath.Match(allowedUser, r.User)
			if match && err == nil {
				userOk = true
			}
		}
		if !userOk {
			return false
		}
	}
	if task.RequireAdmin {
		isAdmin := false
		botCfg.RLock()
		admins := botCfg.adminUsers
		botCfg.RUnlock()
		for _, adminUser := range admins {
			if r.User == adminUser {
				isAdmin = true
				break
			}
		}
		if !isAdmin {
			return false
		}
	}
	return true
}

// jobAvailable does the work of looking up a job and checking whether it's
// available, and messaging the user if it's not. Only called for interactive
// job commands like history, run job, etc. where the user provides a job name.
// Note that changes to login in jobAvailable may need to propagate to
// jobVisible, above.
func (c *botContext) jobAvailable(taskName string) interface{} {
	r := c.makeRobot()
	t := c.tasks.getTaskByName(taskName)
	if t == nil {
		r.Say(fmt.Sprintf("Sorry, I don't have a task named '%s' configured", taskName))
		return nil
	}
	task, _, job := getTask(t)
	isJob := job != nil
	if !isJob {
		r.Say(fmt.Sprintf("Sorry, '%s' isn't a job", taskName))
		return nil
	}
	if c.automaticTask {
		return t
	}
	// If there's already a job initialized, this is a pipeline task for that
	// job, and should be available regardless of channel.
	if !c.jobInitialized && r.Channel != task.Channel {
		c.debugTask(task, fmt.Sprintf("not available in channel '%s'", task.Channel), false)
		r.Say(fmt.Sprintf("Sorry, job '%s' isn't available in this channel, try '%s'", taskName, task.Channel))
		return nil
	}
	if task.RequireAdmin {
		isAdmin := false
		botCfg.RLock()
		admins := botCfg.adminUsers
		botCfg.RUnlock()
		for _, adminUser := range admins {
			if r.User == adminUser {
				isAdmin = true
				break
			}
		}
		if !isAdmin {
			r.Say(fmt.Sprintf("Sorry, '%s' is only available to bot administrators", taskName))
			return nil
		}
	}
	if len(task.Users) > 0 {
		userOk := false
		for _, allowedUser := range task.Users {
			match, err := filepath.Match(allowedUser, r.User)
			if match && err == nil {
				userOk = true
			}
		}
		if !userOk {
			r.Say("Sorry, you're not on the list of allowed users for that job")
			c.debugTask(task, "user is not on the list of allowed users", false)
			return nil
		}
	}
	return t
}
<|endoftext|>


<commit_before>package config

import (
	"encoding/json"
	"io/ioutil"
)

type argument struct {
	Description string      `json:"description"`
	Default     interface{} `json:"default"`
	EnvName     string      `json:"env_name"`
	FlagName    string      `json:"flag_name"`
	Type        string      `json:"type"`
	Required    bool        `json:"required"`
}

func parseJSON(filename string) (map[string]argument, error) {
	b, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	var c map[string]argument
	if err := json.Unmarshal(b, &c); err != nil {
		return nil, err
	}

	return c, nil
}
<commit_msg>making the configuration match case insensitive<commit_after>package config

import (
	"encoding/json"
	"io/ioutil"
	"strings"
)

type argument struct {
	Description string      `json:"description"`
	Default     interface{} `json:"default"`
	EnvName     string      `json:"env_name"`
	FlagName    string      `json:"flag_name"`
	Type        string      `json:"type"`
	Required    bool        `json:"required"`
}

func parseJSON(filename string) (map[string]argument, error) {
	b, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	var c map[string]argument
	if err := json.Unmarshal(b, &c); err != nil {
		return nil, err
	}

	ci := map[string]argument{}
	for k, v := range c {
		ci[strings.ToLower(k)] = v
	}

	return ci, nil
}
<|endoftext|>


<commit_before>package main

// Synopsis:
//
//   Parse("1 2 + 'Hello World' [ '!' append ] 5 times")
//   ;=> [[fun: 1] [fun: 2] [fun: +] [str: 'Hello World'] [stm: '!' append]
//         [fun: 5] [fun: times]]

import (
	"strings"
	"regexp"
)

type Tokens []Token

func (ts *Tokens) String() string {
	str := "["
	for i, t := range *ts {
		if i > 0 {
			str += ", "
		}
		str += t.String()
	}
	return str + "]"
}

type Token struct {
	key string
	val string
}

func (t *Token) String() string {
	return "[" + t.key + ": " + t.val + "]"
}

func NewToken(key, val string) Token {
	return Token{key, val}
}

func FullParse(code string) *Tokens {
	list := new(Tokens)

	for i := 0; i < len(code); i++ {
		temp := ""

		switch c := code[i]; c {
		case '\t', ' ':
			// Ignore whitespace

		case '\n':
			*list = append(*list, NewToken("newline", ""))

		case ';':
			i, temp = parseUntil(i, code, '\n')
			*list = append(*list, NewToken("comment",
				strings.TrimSpace(strings.TrimLeft(temp, ";"))))

		case '.':
			*list = append(*list, NewToken("stm", ""))

		case '\'':
			i++
			i, temp = parseUntil(i, code, '\'')
			*list = append(*list, NewToken("str", temp))

		case '"':
			i++
			i, temp = parseUntil(i, code, '"')
			*list = append(*list, NewToken("str", temp))

		case '[':
			i++
			i, temp = parseMatching(i, code, '[', ']')
			*list = append(*list, NewToken("stm", temp))

		case ':':
			i++
			i, temp = parseUntilWhitespace(i, code)
			*list = append(*list, NewToken("stm", temp))

		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			i, temp = parseUntilWhitespace(i, code)
			*list = append(*list, NewToken("int", temp))

		case '-':
			i, temp = parseUntilWhitespace(i, code)
			matcher, _ := regexp.Compile("-[0-9]+")

			if matcher.MatchString(temp) {
				*list = append(*list, NewToken("int", temp))
			} else {
				*list = append(*list, NewToken("fun", temp))
			}

		default:
			i, temp = parseUntilWhitespace(i, code)
			*list = append(*list, NewToken("fun", temp))

		}
	}

	return list
}

func Parse(code string) *Tokens {
	list := new(Tokens)

	for _, tok := range *FullParse(code) {
		if tok.key != "comment" && tok.key != "newline" {
			*list = append(*list, tok)
		}
	}

	return list
}

func parseUntil(idx int, code string, until uint8) (i int, s string) {
	return parseUntilAny(idx, code, []uint8{until})
}

func parseUntilWhitespace(idx int, code string) (i int, s string) {
	return parseUntilAny(idx, code, []uint8{' ', '\n', '\t'})
}

func parseUntilAny(idx int, code string, untils []uint8) (i int, s string) {
	str := ""

	for i := idx; i < len(code); i++ {
		c := code[i]
		for _, until := range untils {
			if c == until {
				return i, str
			}
		}
		str += string(c)
	}
	return len(code), str
}

func parseMatching(idx int, code string, op, cl uint8) (i int, s string) {
	str := ""

	for i := idx; i < len(code); i++ {
		c := code[i]
		if c == op {
			i++
			f := ""
			i, f = parseMatching(i, code, op, cl)
			str += "[" + f + "]"
		} else if c == cl {
			return i, strings.TrimSpace(str)
		} else {
			str += string(c)
		}
	}
	return len(code), strings.TrimSpace(str)
}
<commit_msg>Add method to add Token to Tokens<commit_after>package main

// Synopsis:
//
//   Parse("1 2 + 'Hello World' [ '!' append ] 5 times")
//   ;=> [[fun: 1] [fun: 2] [fun: +] [str: 'Hello World'] [stm: '!' append]
//         [fun: 5] [fun: times]]

import (
	"strings"
	"regexp"
)

type Tokens []Token

func (ts *Tokens) String() string {
	str := "["
	for i, t := range *ts {
		if i > 0 {
			str += ", "
		}
		str += t.String()
	}
	return str + "]"
}

func (ts *Tokens) Add(tok Token) {
	*ts = append(*ts, tok)
}

func (ts *Tokens) AddToken(key, val string) {
	ts.Add(NewToken(key, val))
}

type Token struct {
	key string
	val string
}

func (t *Token) String() string {
	return "[" + t.key + ": " + t.val + "]"
}

func NewToken(key, val string) Token {
	return Token{key, val}
}

func FullParse(code string) *Tokens {
	list := new(Tokens)

	for i := 0; i < len(code); i++ {
		temp := ""

		switch c := code[i]; c {
		case '\t', ' ':
			// Ignore whitespace

		case '\n':
			list.AddToken("newline", "")

		case ';':
			i, temp = parseUntil(i, code, '\n')
			list.AddToken("comment",
				strings.TrimSpace(strings.TrimLeft(temp, ";")))

		case '.':
			list.AddToken("stm", "")

		case '\'':
			i++
			i, temp = parseUntil(i, code, '\'')
			list.AddToken("str", temp)

		case '"':
			i++
			i, temp = parseUntil(i, code, '"')
			list.AddToken("str", temp)

		case '[':
			i++
			i, temp = parseMatching(i, code, '[', ']')
			list.AddToken("stm", temp)

		case ':':
			i++
			i, temp = parseUntilWhitespace(i, code)
			list.AddToken("stm", temp)

		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			i, temp = parseUntilWhitespace(i, code)
			list.AddToken("int", temp)

		case '-':
			i, temp = parseUntilWhitespace(i, code)
			matcher, _ := regexp.Compile("-[0-9]+")

			if matcher.MatchString(temp) {
				list.AddToken("int", temp)
			} else {
				list.AddToken("fun", temp)
			}

		default:
			i, temp = parseUntilWhitespace(i, code)
			list.AddToken("fun", temp)

		}
	}

	return list
}

func Parse(code string) *Tokens {
	list := new(Tokens)

	for _, tok := range *FullParse(code) {
		if tok.key != "comment" && tok.key != "newline" {
			list.Add(tok)
		}
	}

	return list
}

func parseUntil(idx int, code string, until uint8) (i int, s string) {
	return parseUntilAny(idx, code, []uint8{until})
}

func parseUntilWhitespace(idx int, code string) (i int, s string) {
	return parseUntilAny(idx, code, []uint8{' ', '\n', '\t'})
}

func parseUntilAny(idx int, code string, untils []uint8) (i int, s string) {
	str := ""

	for i := idx; i < len(code); i++ {
		c := code[i]
		for _, until := range untils {
			if c == until {
				return i, str
			}
		}
		str += string(c)
	}
	return len(code), str
}

func parseMatching(idx int, code string, op, cl uint8) (i int, s string) {
	str := ""

	for i := idx; i < len(code); i++ {
		c := code[i]
		if c == op {
			i++
			f := ""
			i, f = parseMatching(i, code, op, cl)
			str += "[" + f + "]"
		} else if c == cl {
			return i, strings.TrimSpace(str)
		} else {
			str += string(c)
		}
	}

	return len(code), strings.TrimSpace(str)
}
<|endoftext|>