<commit_before>package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
	_ "github.com/lib/pq"
)

var servport = ":6862"

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("[X] %s, %s", msg, err)
		panic(fmt.Sprintf("%s, %s", msg, err))
	}
}

func main() {
	log.Println("[i] Server started")

	// Connect to DB
	db, err := sql.Open("postgres", "user=appread dbname='quantifiedSelf' sslmode=disable")
	failOnError(err, "Error connecting to database")
	defer db.Close()

	http.HandleFunc("/data/all/", func(w http.ResponseWriter, r *http.Request) {
		// TODO: Allow filtering via URL
		// Get rows from DB
		var output string
		err := db.QueryRow(`SELECT json_agg(r) FROM (SELECT * FROM trello.cards) r;`).Scan(&output)
		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		fmt.Fprint(w, output)
	})

	// Restful handler
	r := mux.NewRouter()
	r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "dla;jfkdlsajflkdsa;jfk;ldsajfklds;a")
	})

	r.HandleFunc("/api/totals/last/{num}", func(w http.ResponseWriter, r *http.Request) {
		// Grab vars
		vars := mux.Vars(r)

		var output string
		// This is bad... don't do this.... omg
		query := fmt.Sprintf(`SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, end_of_day_total from trello.dailytallies order by day DESC limit %s) r;`, vars["num"])
		err := db.QueryRow(query).Scan(&output)

		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, output)
	})

	r.HandleFunc("/api/diffs/last/{num}", func(w http.ResponseWriter, r *http.Request) {
		// Grab vars
		vars := mux.Vars(r)

		var output string
		// This is bad... don't do this.... omg
		query := fmt.Sprintf(`SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, up_count, down_count, finished_count from trello.dailytallies order by day DESC limit %s) r;`, vars["num"])
		err := db.QueryRow(query).Scan(&output)

		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, output)
	})
	r.PathPrefix("/").Handler(http.FileServer(http.Dir("../ui")))
	http.Handle("/", r)

	// Die gracefully
	// killchan := make(chan os.Signal)
	// signal.Notify(killchan, os.Interrupt, os.Kill)

	log.Println("[i] Serving on ", servport, "\n\tWaiting...")

	log.Fatal(http.ListenAndServe(servport, nil))
	// <-killchan

	log.Println("[i] Shutting down...")
}
<commit_msg>Fixed sql injection<commit_after>package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
	_ "github.com/lib/pq"
)

var servport = ":6862"

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("[X] %s, %s", msg, err)
		panic(fmt.Sprintf("%s, %s", msg, err))
	}
}

func main() {
	log.Println("[i] Server started")

	// Connect to DB
	db, err := sql.Open("postgres", "user=appread dbname='quantifiedSelf' sslmode=disable")
	failOnError(err, "Error connecting to database")
	defer db.Close()

	http.HandleFunc("/data/all/", func(w http.ResponseWriter, r *http.Request) {
		// TODO: Allow filtering via URL
		// Get rows from DB
		var output string
		err := db.QueryRow(`SELECT json_agg(r) FROM (SELECT * FROM trello.cards) r;`).Scan(&output)
		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		fmt.Fprint(w, output)
	})

	// Restful handler
	r := mux.NewRouter()
	r.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "dla;jfkdlsajflkdsa;jfk;ldsajfklds;a")
	})

	r.HandleFunc("/api/totals/last/{num}", func(w http.ResponseWriter, r *http.Request) {
		// Grab vars
		vars := mux.Vars(r)

		var output string
		// This is bad... don't do this.... omg
		query := `SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, end_of_day_total from trello.dailytallies order by day DESC limit $1) r;`
		err := db.QueryRow(query, vars["num"]).Scan(&output)

		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, output)
	})

	r.HandleFunc("/api/diffs/last/{num}", func(w http.ResponseWriter, r *http.Request) {
		// Grab vars
		vars := mux.Vars(r)

		var output string
		// This is bad... don't do this.... omg
		query := `SELECT json_agg(r) FROM (select EXTRACT(epoch FROM day) as day, up_count, down_count, finished_count from trello.dailytallies order by day DESC limit $1) r;`
		err := db.QueryRow(query, vars["num"]).Scan(&output)

		if err != nil {
			log.Println("Error retriving from DB, ", err)
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintln(w, "Error retriving from DB, ", err)
			return
		}

		// Print out returned
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprint(w, output)
	})
	r.PathPrefix("/").Handler(http.FileServer(http.Dir("../ui")))
	http.Handle("/", r)

	// Die gracefully
	// killchan := make(chan os.Signal)
	// signal.Notify(killchan, os.Interrupt, os.Kill)

	log.Println("[i] Serving on ", servport, "\n\tWaiting...")

	log.Fatal(http.ListenAndServe(servport, nil))
	// <-killchan

	log.Println("[i] Shutting down...")
}
<|endoftext|>


<commit_before>package tarantool

import (
	"io"
)

// AnonSlave connects to Tarantool >= 2.3.1 instance and subscribes for changes as anonymous replica.
// Tarantool instance acting as a master sees AnonSlave like anonymous replica.
// AnonSlave can't be used concurrently, route responses from returned channel instead.
type AnonSlave struct {
	Slave
}

// NewAnonSlave returns new AnonSlave instance.
// URI is parsed by url package and therefore should contains any scheme supported by net.Dial.
func NewAnonSlave(uri string, opts ...Options) (as *AnonSlave, err error) {
	s, err := NewSlave(uri, opts...)
	if err != nil {
		return nil, err
	}

	// check tarantool version. Anonymous replica support was added in Tarantool 2.3.1
	if s.Version() < version2_3_1 {
		return nil, ErrOldVersionAnon
	}

	return &AnonSlave{*s}, nil
}

// JoinWithSnap fetch snapshots from Master instance.
// Snapshot logs is available through the given out channel or returned PacketIterator.
// (In truth, Slave itself is returned in PacketIterator wrapper)
func (s *AnonSlave) JoinWithSnap(out ...chan *Packet) (it PacketIterator, err error) {
	if err = s.fetchSnapshot(); err != nil {
		return nil, err
	}

	// set iterator for the Next method
	s.next = s.nextSnap

	if s.isEmptyChan(out...) {
		// no chan means synchronous snapshot scanning
		return s, nil
	}

	defer close(out[0])
	for s.HasNext() {
		out[0] <- s.Packet()
	}

	return nil, s.Err()
}

// Join fetches snapshots using Master instance.
func (s *AnonSlave) Join() (err error) {
	_, err = s.JoinWithSnap()
	if err != nil {
		return err
	}

	for s.HasNext() {
	}

	return s.Err()
}

// Subscribe for DML requests (insert, update, delete, replace, upsert) since vector clock.
// Variadic lsn is start vector clock. Each lsn is one clock in vector (sequentially).
// One lsn is enough for master-slave replica set.
// Subscribe sends requests asynchronously to out channel specified or use synchronous PacketIterator otherwise.
// For anonymous replica it is not necessary to call Join or JoinWithSnap before Subscribe.
func (s *AnonSlave) Subscribe(lsns ...uint64) (it PacketIterator, err error) {
	if len(lsns) == 0 || len(lsns) >= VClockMax {
		return nil, ErrVectorClock
	}

	if err = s.subscribe(lsns...); err != nil {
		return nil, err
	}

	// set iterator for the Next method
	s.next = s.nextXlog

	// Start sending heartbeat messages to master
	go s.heartbeat()

	return s, nil
}

// Attach AnonSlave to Replica Set as an anonymous and subscribe for the new(!) DML requests.
// Attach calls Join and then Subscribe with VClock = s.VClock[1:]...
// If didn't call Join before Attach then you need to set VClock first either manually or using JoinWithSnap.
// Use out chan for asynchronous packet receiving or synchronous PacketIterator otherwise.
// If you need all requests in chan use JoinWithSnap(chan) and then s.Subscribe(s.VClock[1:]...).
func (s *AnonSlave) Attach(out ...chan *Packet) (it PacketIterator, err error) {
	if err = s.Join(); err != nil {
		return nil, err
	}

	// skip reserved zero index of the Vector Clock
	if len(s.VClock) <= 1 {
		return nil, ErrVectorClock
	}

	if it, err = s.Subscribe(s.VClock[1:]...); err != nil {
		return nil, err
	}

	// no chan means synchronous dml request receiving
	if s.isEmptyChan(out...) {
		return it, nil
	}

	// consume new DML requests and send them to the given chan
	go func(out chan *Packet) {
		defer close(out)
		for s.HasNext() {
			out <- s.Packet()
		}
	}(out[0])

	// return nil iterator to avoid concurrent using of the Next method
	return nil, nil
}

func (s *AnonSlave) fetchSnapshot() (err error) {
	pp, err := s.newPacket(&FetchSnapshot{})
	if err != nil {
		return
	}

	if err = s.send(pp); err != nil {
		return err
	}
	s.c.releasePacket(pp)

	if pp, err = s.receive(); err != nil {
		return err
	}
	defer pp.Release()

	p := &Packet{}
	if err := p.UnmarshalBinary(pp.body); err != nil {
		return err
	}

	if p.Cmd != OKCommand {
		s.p = p
		if p.Result == nil {
			return ErrBadResult
		}
		return p.Result.Error
	}

	v := new(VClock)
	_, err = v.UnmarshalMsg(pp.body)
	if err != nil {
		return err
	}

	s.VClock = v.VClock

	return nil
}

// subscribe sends SUBSCRIBE request and waits for VCLOCK response.
func (s *AnonSlave) subscribe(lsns ...uint64) error {
	vc := NewVectorClock(lsns...)
	pp, err := s.newPacket(&Subscribe{
		UUID:   s.UUID,
		VClock: vc,
		Anon:   true,
	})
	if err != nil {
		return err
	}

	if err = s.send(pp); err != nil {
		return err
	}
	s.c.releasePacket(pp)

	if pp, err = s.receive(); err != nil {
		return err
	}
	defer s.c.releasePacket(pp)

	p := &pp.packet
	err = p.UnmarshalBinary(pp.body)
	if err != nil {
		return err
	}
	if p.Cmd != OKCommand {
		s.p = p
		if p.Result == nil {
			return ErrBadResult
		}
		return p.Result.Error
	}

	sub := new(SubscribeResponse)
	_, err = sub.UnmarshalMsg(pp.body)
	if err != nil {
		return err
	}

	if s.ReplicaSet.UUID != "" && s.ReplicaSet.UUID != sub.ReplicaSetUUID {
		return NewUnexpectedReplicaSetUUIDError(s.ReplicaSet.UUID, sub.ReplicaSetUUID)
	}

	s.ReplicaSet.UUID = sub.ReplicaSetUUID
	s.VClock = sub.VClock

	return nil
}

// nextSnap iterates responses on JOIN request.
// At the end it returns io.EOF error and nil packet.
// While iterating all
func (s *AnonSlave) nextSnap() (p *Packet, err error) {
	pp, err := s.receive()
	if err != nil {
		return nil, err
	}
	defer s.c.releasePacket(pp)

	p = &Packet{}
	err = p.UnmarshalBinary(pp.body)
	if err != nil {
		return nil, err
	}

	// we have to parse snapshot logs to find replica set instances, UUID
	switch p.Cmd {
	case InsertCommand:
		q := p.Request.(*Insert)
		if q.Space == SpaceSchema {
			key := q.Tuple[0].(string)
			if key == SchemaKeyClusterUUID {
				if s.ReplicaSet.UUID != "" && s.ReplicaSet.UUID != q.Tuple[1].(string) {
					return nil, NewUnexpectedReplicaSetUUIDError(s.ReplicaSet.UUID, q.Tuple[1].(string))
				}
				s.ReplicaSet.UUID = q.Tuple[1].(string)
			}
		}
	case OKCommand:
		v := new(VClock)
		_, err = v.UnmarshalMsg(pp.body)
		if err != nil {
			return nil, err
		}
		// ignore this VClock for anon replica

		return nil, io.EOF
	}

	return p, nil
}
<commit_msg>Send cluster UUID in Subscribe request<commit_after>package tarantool

import (
	"io"
)

// AnonSlave connects to Tarantool >= 2.3.1 instance and subscribes for changes as anonymous replica.
// Tarantool instance acting as a master sees AnonSlave like anonymous replica.
// AnonSlave can't be used concurrently, route responses from returned channel instead.
type AnonSlave struct {
	Slave
}

// NewAnonSlave returns new AnonSlave instance.
// URI is parsed by url package and therefore should contains any scheme supported by net.Dial.
func NewAnonSlave(uri string, opts ...Options) (as *AnonSlave, err error) {
	s, err := NewSlave(uri, opts...)
	if err != nil {
		return nil, err
	}

	// check tarantool version. Anonymous replica support was added in Tarantool 2.3.1
	if s.Version() < version2_3_1 {
		return nil, ErrOldVersionAnon
	}

	return &AnonSlave{*s}, nil
}

// JoinWithSnap fetch snapshots from Master instance.
// Snapshot logs is available through the given out channel or returned PacketIterator.
// (In truth, Slave itself is returned in PacketIterator wrapper)
func (s *AnonSlave) JoinWithSnap(out ...chan *Packet) (it PacketIterator, err error) {
	if err = s.fetchSnapshot(); err != nil {
		return nil, err
	}

	// set iterator for the Next method
	s.next = s.nextSnap

	if s.isEmptyChan(out...) {
		// no chan means synchronous snapshot scanning
		return s, nil
	}

	defer close(out[0])
	for s.HasNext() {
		out[0] <- s.Packet()
	}

	return nil, s.Err()
}

// Join fetches snapshots using Master instance.
func (s *AnonSlave) Join() (err error) {
	_, err = s.JoinWithSnap()
	if err != nil {
		return err
	}

	for s.HasNext() {
	}

	return s.Err()
}

// Subscribe for DML requests (insert, update, delete, replace, upsert) since vector clock.
// Variadic lsn is start vector clock. Each lsn is one clock in vector (sequentially).
// One lsn is enough for master-slave replica set.
// Subscribe sends requests asynchronously to out channel specified or use synchronous PacketIterator otherwise.
// For anonymous replica it is not necessary to call Join or JoinWithSnap before Subscribe.
func (s *AnonSlave) Subscribe(lsns ...uint64) (it PacketIterator, err error) {
	if len(lsns) == 0 || len(lsns) >= VClockMax {
		return nil, ErrVectorClock
	}

	if err = s.subscribe(lsns...); err != nil {
		return nil, err
	}

	// set iterator for the Next method
	s.next = s.nextXlog

	// Start sending heartbeat messages to master
	go s.heartbeat()

	return s, nil
}

// Attach AnonSlave to Replica Set as an anonymous and subscribe for the new(!) DML requests.
// Attach calls Join and then Subscribe with VClock = s.VClock[1:]...
// If didn't call Join before Attach then you need to set VClock first either manually or using JoinWithSnap.
// Use out chan for asynchronous packet receiving or synchronous PacketIterator otherwise.
// If you need all requests in chan use JoinWithSnap(chan) and then s.Subscribe(s.VClock[1:]...).
func (s *AnonSlave) Attach(out ...chan *Packet) (it PacketIterator, err error) {
	if err = s.Join(); err != nil {
		return nil, err
	}

	// skip reserved zero index of the Vector Clock
	if len(s.VClock) <= 1 {
		return nil, ErrVectorClock
	}

	if it, err = s.Subscribe(s.VClock[1:]...); err != nil {
		return nil, err
	}

	// no chan means synchronous dml request receiving
	if s.isEmptyChan(out...) {
		return it, nil
	}

	// consume new DML requests and send them to the given chan
	go func(out chan *Packet) {
		defer close(out)
		for s.HasNext() {
			out <- s.Packet()
		}
	}(out[0])

	// return nil iterator to avoid concurrent using of the Next method
	return nil, nil
}

func (s *AnonSlave) fetchSnapshot() (err error) {
	pp, err := s.newPacket(&FetchSnapshot{})
	if err != nil {
		return
	}

	if err = s.send(pp); err != nil {
		return err
	}
	s.c.releasePacket(pp)

	if pp, err = s.receive(); err != nil {
		return err
	}
	defer pp.Release()

	p := &Packet{}
	if err := p.UnmarshalBinary(pp.body); err != nil {
		return err
	}

	if p.Cmd != OKCommand {
		s.p = p
		if p.Result == nil {
			return ErrBadResult
		}
		return p.Result.Error
	}

	v := new(VClock)
	_, err = v.UnmarshalMsg(pp.body)
	if err != nil {
		return err
	}

	s.VClock = v.VClock

	return nil
}

// subscribe sends SUBSCRIBE request and waits for VCLOCK response.
func (s *AnonSlave) subscribe(lsns ...uint64) error {
	vc := NewVectorClock(lsns...)
	pp, err := s.newPacket(&Subscribe{
		UUID:           s.UUID,
		ReplicaSetUUID: s.ReplicaSet.UUID,
		VClock:         vc,
		Anon:           true,
	})
	if err != nil {
		return err
	}

	if err = s.send(pp); err != nil {
		return err
	}
	s.c.releasePacket(pp)

	if pp, err = s.receive(); err != nil {
		return err
	}
	defer s.c.releasePacket(pp)

	p := &pp.packet
	err = p.UnmarshalBinary(pp.body)
	if err != nil {
		return err
	}
	if p.Cmd != OKCommand {
		s.p = p
		if p.Result == nil {
			return ErrBadResult
		}
		return p.Result.Error
	}

	sub := new(SubscribeResponse)
	_, err = sub.UnmarshalMsg(pp.body)
	if err != nil {
		return err
	}

	if s.ReplicaSet.UUID != "" && s.ReplicaSet.UUID != sub.ReplicaSetUUID {
		return NewUnexpectedReplicaSetUUIDError(s.ReplicaSet.UUID, sub.ReplicaSetUUID)
	}

	s.ReplicaSet.UUID = sub.ReplicaSetUUID
	s.VClock = sub.VClock

	return nil
}

// nextSnap iterates responses on JOIN request.
// At the end it returns io.EOF error and nil packet.
// While iterating all
func (s *AnonSlave) nextSnap() (p *Packet, err error) {
	pp, err := s.receive()
	if err != nil {
		return nil, err
	}
	defer s.c.releasePacket(pp)

	p = &Packet{}
	err = p.UnmarshalBinary(pp.body)
	if err != nil {
		return nil, err
	}

	// we have to parse snapshot logs to find replica set instances, UUID
	switch p.Cmd {
	case InsertCommand:
		q := p.Request.(*Insert)
		if q.Space == SpaceSchema {
			key := q.Tuple[0].(string)
			if key == SchemaKeyClusterUUID {
				if s.ReplicaSet.UUID != "" && s.ReplicaSet.UUID != q.Tuple[1].(string) {
					return nil, NewUnexpectedReplicaSetUUIDError(s.ReplicaSet.UUID, q.Tuple[1].(string))
				}
				s.ReplicaSet.UUID = q.Tuple[1].(string)
			}
		}
	case OKCommand:
		v := new(VClock)
		_, err = v.UnmarshalMsg(pp.body)
		if err != nil {
			return nil, err
		}
		// ignore this VClock for anon replica

		return nil, io.EOF
	}

	return p, nil
}
<|endoftext|>


<commit_before>package auth

import (
	context2 "context"
	"encoding/json"
	"fmt"

	"github.com/silenceper/wechat/v2/miniprogram/context"
	"github.com/silenceper/wechat/v2/util"
)

const (
	code2SessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"

	checkEncryptedDataURL = "https://api.weixin.qq.com/wxa/business/checkencryptedmsg?access_token=%s"
)

// Auth 登录/用户信息
type Auth struct {
	*context.Context
}

// NewAuth new auth
func NewAuth(ctx *context.Context) *Auth {
	return &Auth{ctx}
}

// ResCode2Session 登录凭证校验的返回结果
type ResCode2Session struct {
	util.CommonError

	OpenID     string `json:"openid"`      // 用户唯一标识
	SessionKey string `json:"session_key"` // 会话密钥
	UnionID    string `json:"unionid"`     // 用户在开放平台的唯一标识符，在满足UnionID下发条件的情况下会返回
}

// RspCheckEncryptedData .
type RspCheckEncryptedData struct {
	util.CommonError

	Vaild      bool `json:"vaild"`       // 是否是合法的数据
	CreateTime uint `json:"create_time"` // 加密数据生成的时间戳
}

// Code2Session 登录凭证校验。
func (auth *Auth) Code2Session(jsCode string) (result ResCode2Session, err error) {
	return auth.Code2SessionContext(context2.Background(), jsCode)
}

// Code2SessionContext 登录凭证校验。
func (auth *Auth) Code2SessionContext(ctx context2.Context, jsCode string) (result ResCode2Session, err error) {
	var response []byte
	if response, err = util.HTTPGetContext(ctx, fmt.Sprintf(code2SessionURL, auth.AppID, auth.AppSecret, jsCode)); err != nil {
		return
	}
	if err = json.Unmarshal(response, &result); err != nil {
		return
	}
	if result.ErrCode != 0 {
		err = fmt.Errorf("Code2Session error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
		return
	}
	return
}

// GetPaidUnionID 用户支付完成后，获取该用户的 UnionId，无需用户授权
func (auth *Auth) GetPaidUnionID() {
	// TODO
}

// CheckEncryptedData .检查加密信息是否由微信生成（当前只支持手机号加密数据），只能检测最近3天生成的加密数据
func (auth *Auth) CheckEncryptedData(encryptedMsgHash string) (result RspCheckEncryptedData, err error) {
	return auth.CheckEncryptedDataContext(context2.Background(), encryptedMsgHash)
}

// CheckEncryptedDataContext .检查加密信息是否由微信生成（当前只支持手机号加密数据），只能检测最近3天生成的加密数据
func (auth *Auth) CheckEncryptedDataContext(ctx context2.Context, encryptedMsgHash string) (result RspCheckEncryptedData, err error) {
	var response []byte
	var (
		at string
	)
	if at, err = auth.GetAccessToken(); err != nil {
		return
	}
	if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(checkEncryptedDataURL, at), "encrypted_msg_hash="+encryptedMsgHash); err != nil {
		return
	}
	if err = util.DecodeWithError(response, &result, "CheckEncryptedDataAuth"); err != nil {
		return
	}
	return
}
<commit_msg>微信小程序新版获取手机号授权接口 (#528)<commit_after>package auth

import (
	context2 "context"
	"encoding/json"
	"fmt"

	"github.com/silenceper/wechat/v2/miniprogram/context"
	"github.com/silenceper/wechat/v2/util"
)

const (
	code2SessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"

	checkEncryptedDataURL = "https://api.weixin.qq.com/wxa/business/checkencryptedmsg?access_token=%s"

	getPhoneNumber = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s"
)

// Auth 登录/用户信息
type Auth struct {
	*context.Context
}

// NewAuth new auth
func NewAuth(ctx *context.Context) *Auth {
	return &Auth{ctx}
}

// ResCode2Session 登录凭证校验的返回结果
type ResCode2Session struct {
	util.CommonError

	OpenID     string `json:"openid"`      // 用户唯一标识
	SessionKey string `json:"session_key"` // 会话密钥
	UnionID    string `json:"unionid"`     // 用户在开放平台的唯一标识符，在满足UnionID下发条件的情况下会返回
}

// RspCheckEncryptedData .
type RspCheckEncryptedData struct {
	util.CommonError

	Vaild      bool `json:"vaild"`       // 是否是合法的数据
	CreateTime uint `json:"create_time"` // 加密数据生成的时间戳
}

// Code2Session 登录凭证校验。
func (auth *Auth) Code2Session(jsCode string) (result ResCode2Session, err error) {
	return auth.Code2SessionContext(context2.Background(), jsCode)
}

// Code2SessionContext 登录凭证校验。
func (auth *Auth) Code2SessionContext(ctx context2.Context, jsCode string) (result ResCode2Session, err error) {
	var response []byte
	if response, err = util.HTTPGetContext(ctx, fmt.Sprintf(code2SessionURL, auth.AppID, auth.AppSecret, jsCode)); err != nil {
		return
	}
	if err = json.Unmarshal(response, &result); err != nil {
		return
	}
	if result.ErrCode != 0 {
		err = fmt.Errorf("Code2Session error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
		return
	}
	return
}

// GetPaidUnionID 用户支付完成后，获取该用户的 UnionId，无需用户授权
func (auth *Auth) GetPaidUnionID() {
	// TODO
}

// CheckEncryptedData .检查加密信息是否由微信生成（当前只支持手机号加密数据），只能检测最近3天生成的加密数据
func (auth *Auth) CheckEncryptedData(encryptedMsgHash string) (result RspCheckEncryptedData, err error) {
	return auth.CheckEncryptedDataContext(context2.Background(), encryptedMsgHash)
}

// CheckEncryptedDataContext .检查加密信息是否由微信生成（当前只支持手机号加密数据），只能检测最近3天生成的加密数据
func (auth *Auth) CheckEncryptedDataContext(ctx context2.Context, encryptedMsgHash string) (result RspCheckEncryptedData, err error) {
	var response []byte
	var (
		at string
	)
	if at, err = auth.GetAccessToken(); err != nil {
		return
	}
	if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(checkEncryptedDataURL, at), "encrypted_msg_hash="+encryptedMsgHash); err != nil {
		return
	}
	if err = util.DecodeWithError(response, &result, "CheckEncryptedDataAuth"); err != nil {
		return
	}
	return
}

// GetPhoneNumberResponse 新版获取用户手机号响应结构体
type GetPhoneNumberResponse struct {
	util.CommonError

	PhoneInfo PhoneInfo `json:"phone_info"`
}

// PhoneInfo 获取用户手机号内容
type PhoneInfo struct {
	PhoneNumber     string `json:"phoneNumber"`     // 用户绑定的手机号
	PurePhoneNumber string `json:"purePhoneNumber"` // 没有区号的手机号
	CountryCode     string `json:"countryCode"`     // 区号
	WaterMark       struct {
		Timestamp int64  `json:"timestamp"`
		AppID     string `json:"appid"`
	} `json:"watermark"` // 数据水印
}

// GetPhoneNumber 小程序通过code获取用户手机号
func (auth *Auth) GetPhoneNumber(code string) (result GetPhoneNumberResponse, err error) {
	var response []byte
	var (
		at string
	)
	if at, err = auth.GetAccessToken(); err != nil {
		return
	}
	body := map[string]interface{}{
		"code": code,
	}
	if response, err = util.PostJSON(fmt.Sprintf(getPhoneNumber, at), body); err != nil {
		return
	}
	if err = util.DecodeWithError(response, &result, "phonenumber.getPhoneNumber"); err != nil {
		return
	}
	return
}
<|endoftext|>


<commit_before>// +build !ignore_autogenerated

/*
Copyright 2017 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.
*/

// This file was autogenerated by conversion-gen. Do not edit it manually!

package v1alpha1

import (
	conversion "k8s.io/apimachinery/pkg/conversion"
	runtime "k8s.io/apimachinery/pkg/runtime"
	apiregistration "k8s.io/kube-aggregator/pkg/apis/apiregistration"
	unsafe "unsafe"
)

func init() {
	SchemeBuilder.Register(RegisterConversions)
}

// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(scheme *runtime.Scheme) error {
	return scheme.AddGeneratedConversionFuncs(
		Convert_v1alpha1_APIService_To_apiregistration_APIService,
		Convert_apiregistration_APIService_To_v1alpha1_APIService,
		Convert_v1alpha1_APIServiceList_To_apiregistration_APIServiceList,
		Convert_apiregistration_APIServiceList_To_v1alpha1_APIServiceList,
		Convert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec,
		Convert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec,
		Convert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus,
		Convert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus,
		Convert_v1alpha1_ServiceReference_To_apiregistration_ServiceReference,
		Convert_apiregistration_ServiceReference_To_v1alpha1_ServiceReference,
	)
}

func autoConvert_v1alpha1_APIService_To_apiregistration_APIService(in *APIService, out *apiregistration.APIService, s conversion.Scope) error {
	out.ObjectMeta = in.ObjectMeta
	if err := Convert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec(&in.Spec, &out.Spec, s); err != nil {
		return err
	}
	if err := Convert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus(&in.Status, &out.Status, s); err != nil {
		return err
	}
	return nil
}

func Convert_v1alpha1_APIService_To_apiregistration_APIService(in *APIService, out *apiregistration.APIService, s conversion.Scope) error {
	return autoConvert_v1alpha1_APIService_To_apiregistration_APIService(in, out, s)
}

func autoConvert_apiregistration_APIService_To_v1alpha1_APIService(in *apiregistration.APIService, out *APIService, s conversion.Scope) error {
	out.ObjectMeta = in.ObjectMeta
	if err := Convert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec(&in.Spec, &out.Spec, s); err != nil {
		return err
	}
	if err := Convert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus(&in.Status, &out.Status, s); err != nil {
		return err
	}
	return nil
}

func Convert_apiregistration_APIService_To_v1alpha1_APIService(in *apiregistration.APIService, out *APIService, s conversion.Scope) error {
	return autoConvert_apiregistration_APIService_To_v1alpha1_APIService(in, out, s)
}

func autoConvert_v1alpha1_APIServiceList_To_apiregistration_APIServiceList(in *APIServiceList, out *apiregistration.APIServiceList, s conversion.Scope) error {
	out.ListMeta = in.ListMeta
	out.Items = *(*[]apiregistration.APIService)(unsafe.Pointer(&in.Items))
	return nil
}

func Convert_v1alpha1_APIServiceList_To_apiregistration_APIServiceList(in *APIServiceList, out *apiregistration.APIServiceList, s conversion.Scope) error {
	return autoConvert_v1alpha1_APIServiceList_To_apiregistration_APIServiceList(in, out, s)
}

func autoConvert_apiregistration_APIServiceList_To_v1alpha1_APIServiceList(in *apiregistration.APIServiceList, out *APIServiceList, s conversion.Scope) error {
	out.ListMeta = in.ListMeta
	if in.Items == nil {
		out.Items = make([]APIService, 0)
	} else {
		out.Items = *(*[]APIService)(unsafe.Pointer(&in.Items))
	}
	return nil
}

func Convert_apiregistration_APIServiceList_To_v1alpha1_APIServiceList(in *apiregistration.APIServiceList, out *APIServiceList, s conversion.Scope) error {
	return autoConvert_apiregistration_APIServiceList_To_v1alpha1_APIServiceList(in, out, s)
}

func autoConvert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec(in *APIServiceSpec, out *apiregistration.APIServiceSpec, s conversion.Scope) error {
	out.Service = (*apiregistration.ServiceReference)(unsafe.Pointer(in.Service))
	out.Group = in.Group
	out.Version = in.Version
	out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
	out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle))
	out.Priority = in.Priority
	return nil
}

func Convert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec(in *APIServiceSpec, out *apiregistration.APIServiceSpec, s conversion.Scope) error {
	return autoConvert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec(in, out, s)
}

func autoConvert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec(in *apiregistration.APIServiceSpec, out *APIServiceSpec, s conversion.Scope) error {
	out.Service = (*ServiceReference)(unsafe.Pointer(in.Service))
	out.Group = in.Group
	out.Version = in.Version
	out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
	if in.CABundle == nil {
		out.CABundle = make([]byte, 0)
	} else {
		out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle))
	}
	out.Priority = in.Priority
	return nil
}

func Convert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec(in *apiregistration.APIServiceSpec, out *APIServiceSpec, s conversion.Scope) error {
	return autoConvert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec(in, out, s)
}

func autoConvert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus(in *APIServiceStatus, out *apiregistration.APIServiceStatus, s conversion.Scope) error {
	return nil
}

func Convert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus(in *APIServiceStatus, out *apiregistration.APIServiceStatus, s conversion.Scope) error {
	return autoConvert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus(in, out, s)
}

func autoConvert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus(in *apiregistration.APIServiceStatus, out *APIServiceStatus, s conversion.Scope) error {
	return nil
}

func Convert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus(in *apiregistration.APIServiceStatus, out *APIServiceStatus, s conversion.Scope) error {
	return autoConvert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus(in, out, s)
}

func autoConvert_v1alpha1_ServiceReference_To_apiregistration_ServiceReference(in *ServiceReference, out *apiregistration.ServiceReference, s conversion.Scope) error {
	out.Namespace = in.Namespace
	out.Name = in.Name
	return nil
}

func Convert_v1alpha1_ServiceReference_To_apiregistration_ServiceReference(in *ServiceReference, out *apiregistration.ServiceReference, s conversion.Scope) error {
	return autoConvert_v1alpha1_ServiceReference_To_apiregistration_ServiceReference(in, out, s)
}

func autoConvert_apiregistration_ServiceReference_To_v1alpha1_ServiceReference(in *apiregistration.ServiceReference, out *ServiceReference, s conversion.Scope) error {
	out.Namespace = in.Namespace
	out.Name = in.Name
	return nil
}

func Convert_apiregistration_ServiceReference_To_v1alpha1_ServiceReference(in *apiregistration.ServiceReference, out *ServiceReference, s conversion.Scope) error {
	return autoConvert_apiregistration_ServiceReference_To_v1alpha1_ServiceReference(in, out, s)
}
<commit_msg>Conversion generated code changes for golint fix<commit_after>// +build !ignore_autogenerated

/*
Copyright 2017 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.
*/

// This file was autogenerated by conversion-gen. Do not edit it manually!

package v1alpha1

import (
	conversion "k8s.io/apimachinery/pkg/conversion"
	runtime "k8s.io/apimachinery/pkg/runtime"
	apiregistration "k8s.io/kube-aggregator/pkg/apis/apiregistration"
	unsafe "unsafe"
)

func init() {
	SchemeBuilder.Register(RegisterConversions)
}

// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(scheme *runtime.Scheme) error {
	return scheme.AddGeneratedConversionFuncs(
		Convert_v1alpha1_APIService_To_apiregistration_APIService,
		Convert_apiregistration_APIService_To_v1alpha1_APIService,
		Convert_v1alpha1_APIServiceList_To_apiregistration_APIServiceList,
		Convert_apiregistration_APIServiceList_To_v1alpha1_APIServiceList,
		Convert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec,
		Convert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec,
		Convert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus,
		Convert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus,
		Convert_v1alpha1_ServiceReference_To_apiregistration_ServiceReference,
		Convert_apiregistration_ServiceReference_To_v1alpha1_ServiceReference,
	)
}

func autoConvert_v1alpha1_APIService_To_apiregistration_APIService(in *APIService, out *apiregistration.APIService, s conversion.Scope) error {
	out.ObjectMeta = in.ObjectMeta
	if err := Convert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec(&in.Spec, &out.Spec, s); err != nil {
		return err
	}
	if err := Convert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus(&in.Status, &out.Status, s); err != nil {
		return err
	}
	return nil
}

// Convert_v1alpha1_APIService_To_apiregistration_APIService is an autogenerated conversion function.
func Convert_v1alpha1_APIService_To_apiregistration_APIService(in *APIService, out *apiregistration.APIService, s conversion.Scope) error {
	return autoConvert_v1alpha1_APIService_To_apiregistration_APIService(in, out, s)
}

func autoConvert_apiregistration_APIService_To_v1alpha1_APIService(in *apiregistration.APIService, out *APIService, s conversion.Scope) error {
	out.ObjectMeta = in.ObjectMeta
	if err := Convert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec(&in.Spec, &out.Spec, s); err != nil {
		return err
	}
	if err := Convert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus(&in.Status, &out.Status, s); err != nil {
		return err
	}
	return nil
}

// Convert_apiregistration_APIService_To_v1alpha1_APIService is an autogenerated conversion function.
func Convert_apiregistration_APIService_To_v1alpha1_APIService(in *apiregistration.APIService, out *APIService, s conversion.Scope) error {
	return autoConvert_apiregistration_APIService_To_v1alpha1_APIService(in, out, s)
}

func autoConvert_v1alpha1_APIServiceList_To_apiregistration_APIServiceList(in *APIServiceList, out *apiregistration.APIServiceList, s conversion.Scope) error {
	out.ListMeta = in.ListMeta
	out.Items = *(*[]apiregistration.APIService)(unsafe.Pointer(&in.Items))
	return nil
}

// Convert_v1alpha1_APIServiceList_To_apiregistration_APIServiceList is an autogenerated conversion function.
func Convert_v1alpha1_APIServiceList_To_apiregistration_APIServiceList(in *APIServiceList, out *apiregistration.APIServiceList, s conversion.Scope) error {
	return autoConvert_v1alpha1_APIServiceList_To_apiregistration_APIServiceList(in, out, s)
}

func autoConvert_apiregistration_APIServiceList_To_v1alpha1_APIServiceList(in *apiregistration.APIServiceList, out *APIServiceList, s conversion.Scope) error {
	out.ListMeta = in.ListMeta
	if in.Items == nil {
		out.Items = make([]APIService, 0)
	} else {
		out.Items = *(*[]APIService)(unsafe.Pointer(&in.Items))
	}
	return nil
}

// Convert_apiregistration_APIServiceList_To_v1alpha1_APIServiceList is an autogenerated conversion function.
func Convert_apiregistration_APIServiceList_To_v1alpha1_APIServiceList(in *apiregistration.APIServiceList, out *APIServiceList, s conversion.Scope) error {
	return autoConvert_apiregistration_APIServiceList_To_v1alpha1_APIServiceList(in, out, s)
}

func autoConvert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec(in *APIServiceSpec, out *apiregistration.APIServiceSpec, s conversion.Scope) error {
	out.Service = (*apiregistration.ServiceReference)(unsafe.Pointer(in.Service))
	out.Group = in.Group
	out.Version = in.Version
	out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
	out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle))
	out.Priority = in.Priority
	return nil
}

// Convert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec is an autogenerated conversion function.
func Convert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec(in *APIServiceSpec, out *apiregistration.APIServiceSpec, s conversion.Scope) error {
	return autoConvert_v1alpha1_APIServiceSpec_To_apiregistration_APIServiceSpec(in, out, s)
}

func autoConvert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec(in *apiregistration.APIServiceSpec, out *APIServiceSpec, s conversion.Scope) error {
	out.Service = (*ServiceReference)(unsafe.Pointer(in.Service))
	out.Group = in.Group
	out.Version = in.Version
	out.InsecureSkipTLSVerify = in.InsecureSkipTLSVerify
	if in.CABundle == nil {
		out.CABundle = make([]byte, 0)
	} else {
		out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle))
	}
	out.Priority = in.Priority
	return nil
}

// Convert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec is an autogenerated conversion function.
func Convert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec(in *apiregistration.APIServiceSpec, out *APIServiceSpec, s conversion.Scope) error {
	return autoConvert_apiregistration_APIServiceSpec_To_v1alpha1_APIServiceSpec(in, out, s)
}

func autoConvert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus(in *APIServiceStatus, out *apiregistration.APIServiceStatus, s conversion.Scope) error {
	return nil
}

// Convert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus is an autogenerated conversion function.
func Convert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus(in *APIServiceStatus, out *apiregistration.APIServiceStatus, s conversion.Scope) error {
	return autoConvert_v1alpha1_APIServiceStatus_To_apiregistration_APIServiceStatus(in, out, s)
}

func autoConvert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus(in *apiregistration.APIServiceStatus, out *APIServiceStatus, s conversion.Scope) error {
	return nil
}

// Convert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus is an autogenerated conversion function.
func Convert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus(in *apiregistration.APIServiceStatus, out *APIServiceStatus, s conversion.Scope) error {
	return autoConvert_apiregistration_APIServiceStatus_To_v1alpha1_APIServiceStatus(in, out, s)
}

func autoConvert_v1alpha1_ServiceReference_To_apiregistration_ServiceReference(in *ServiceReference, out *apiregistration.ServiceReference, s conversion.Scope) error {
	out.Namespace = in.Namespace
	out.Name = in.Name
	return nil
}

// Convert_v1alpha1_ServiceReference_To_apiregistration_ServiceReference is an autogenerated conversion function.
func Convert_v1alpha1_ServiceReference_To_apiregistration_ServiceReference(in *ServiceReference, out *apiregistration.ServiceReference, s conversion.Scope) error {
	return autoConvert_v1alpha1_ServiceReference_To_apiregistration_ServiceReference(in, out, s)
}

func autoConvert_apiregistration_ServiceReference_To_v1alpha1_ServiceReference(in *apiregistration.ServiceReference, out *ServiceReference, s conversion.Scope) error {
	out.Namespace = in.Namespace
	out.Name = in.Name
	return nil
}

// Convert_apiregistration_ServiceReference_To_v1alpha1_ServiceReference is an autogenerated conversion function.
func Convert_apiregistration_ServiceReference_To_v1alpha1_ServiceReference(in *apiregistration.ServiceReference, out *ServiceReference, s conversion.Scope) error {
	return autoConvert_apiregistration_ServiceReference_To_v1alpha1_ServiceReference(in, out, s)
}
<|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 openstack

import (
	"context"
	"fmt"
	"regexp"

	"github.com/golang/glog"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/kubernetes/pkg/cloudprovider"
)

// Instances encapsulates an implementation of Instances for OpenStack.
type Instances struct {
	compute *gophercloud.ServiceClient
	opts    MetadataOpts
}

// Instances returns an implementation of Instances for OpenStack.
func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
	glog.V(4).Info("openstack.Instances() called")

	compute, err := os.NewComputeV2()
	if err != nil {
		return nil, false
	}

	glog.V(4).Info("Claiming to support Instances")

	return &Instances{
		compute: compute,
		opts:    os.metadataOpts,
	}, true
}

// CurrentNodeName implements Instances.CurrentNodeName
// Note this is *not* necessarily the same as hostname.
func (i *Instances) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
	md, err := getMetadata(i.opts.SearchOrder)
	if err != nil {
		return "", err
	}
	return types.NodeName(md.Hostname), nil
}

// AddSSHKeyToAllInstances is not implemented for OpenStack
func (i *Instances) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
	return cloudprovider.NotImplemented
}

// NodeAddresses implements Instances.NodeAddresses
func (i *Instances) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) {
	glog.V(4).Infof("NodeAddresses(%v) called", name)

	addrs, err := getAddressesByName(i.compute, name)
	if err != nil {
		return nil, err
	}

	glog.V(4).Infof("NodeAddresses(%v) => %v", name, addrs)
	return addrs, nil
}

// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (i *Instances) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
	instanceID, err := instanceIDFromProviderID(providerID)

	if err != nil {
		return []v1.NodeAddress{}, err
	}

	server, err := servers.Get(i.compute, instanceID).Extract()

	if err != nil {
		return []v1.NodeAddress{}, err
	}

	addresses, err := nodeAddresses(server)
	if err != nil {
		return []v1.NodeAddress{}, err
	}

	return addresses, nil
}

// ExternalID returns the cloud provider ID of the specified instance (deprecated).
func (i *Instances) ExternalID(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)
	if err != nil {
		if err == ErrNotFound {
			return "", cloudprovider.InstanceNotFound
		}
		return "", err
	}
	return srv.ID, nil
}

// InstanceExistsByProviderID returns true if the instance with the given provider id still exist.
// If false is returned with no error, the instance will be immediately deleted by the cloud controller manager.
func (i *Instances) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
	instanceID, err := instanceIDFromProviderID(providerID)
	if err != nil {
		return false, err
	}

	_, err = servers.Get(i.compute, instanceID).Extract()
	if err != nil {
		if isNotFound(err) {
			return false, nil
		}
		return false, err
	}

	return true, nil
}

// InstanceID returns the kubelet's cloud provider ID.
func (os *OpenStack) InstanceID() (string, error) {
	if len(os.localInstanceID) == 0 {
		id, err := readInstanceID(os.metadataOpts.SearchOrder)
		if err != nil {
			return "", err
		}
		os.localInstanceID = id
	}
	return os.localInstanceID, nil
}

// InstanceID returns the cloud provider ID of the specified instance.
func (i *Instances) InstanceID(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)
	if err != nil {
		if err == ErrNotFound {
			return "", cloudprovider.InstanceNotFound
		}
		return "", err
	}
	// In the future it is possible to also return an endpoint as:
	// <endpoint>/<instanceid>
	return "/" + srv.ID, nil
}

// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (i *Instances) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
	instanceID, err := instanceIDFromProviderID(providerID)

	if err != nil {
		return "", err
	}

	server, err := servers.Get(i.compute, instanceID).Extract()

	if err != nil {
		return "", err
	}

	return srvInstanceType(server)
}

// InstanceType returns the type of the specified instance.
func (i *Instances) InstanceType(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)

	if err != nil {
		return "", err
	}

	return srvInstanceType(srv)
}

func srvInstanceType(srv *servers.Server) (string, error) {
	keys := []string{"name", "id", "original_name"}
	for _, key := range keys {
		val, found := srv.Flavor[key]
		if found {
			flavor, ok := val.(string)
			if ok {
				return flavor, nil
			}
		}
	}
	return "", fmt.Errorf("flavor name/id not found")
}

// instanceIDFromProviderID splits a provider's id and return instanceID.
// A providerID is build out of '${ProviderName}:///${instance-id}'which contains ':///'.
// See cloudprovider.GetInstanceProviderID and Instances.InstanceID.
func instanceIDFromProviderID(providerID string) (instanceID string, err error) {
	// If Instances.InstanceID or cloudprovider.GetInstanceProviderID is changed, the regexp should be changed too.
	var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `:///([^/]+)$`)

	matches := providerIDRegexp.FindStringSubmatch(providerID)
	if len(matches) != 2 {
		return "", fmt.Errorf("ProviderID \"%s\" didn't match expected format \"openstack:///InstanceID\"", providerID)
	}
	return matches[1], nil
}
<commit_msg>Split out the hostname when default dhcp_domain is used in nova.conf<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 openstack

import (
	"context"
	"fmt"
	"regexp"
	"strings"

	"github.com/golang/glog"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/kubernetes/pkg/cloudprovider"
)

// Instances encapsulates an implementation of Instances for OpenStack.
type Instances struct {
	compute *gophercloud.ServiceClient
	opts    MetadataOpts
}

// Instances returns an implementation of Instances for OpenStack.
func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
	glog.V(4).Info("openstack.Instances() called")

	compute, err := os.NewComputeV2()
	if err != nil {
		return nil, false
	}

	glog.V(4).Info("Claiming to support Instances")

	return &Instances{
		compute: compute,
		opts:    os.metadataOpts,
	}, true
}

// CurrentNodeName implements Instances.CurrentNodeName
// Note this is *not* necessarily the same as hostname.
func (i *Instances) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
	md, err := getMetadata(i.opts.SearchOrder)
	if err != nil {
		return "", err
	}
	return types.NodeName(strings.Split(md.Hostname, ".")[0]), nil
}

// AddSSHKeyToAllInstances is not implemented for OpenStack
func (i *Instances) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
	return cloudprovider.NotImplemented
}

// NodeAddresses implements Instances.NodeAddresses
func (i *Instances) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) {
	glog.V(4).Infof("NodeAddresses(%v) called", name)

	addrs, err := getAddressesByName(i.compute, name)
	if err != nil {
		return nil, err
	}

	glog.V(4).Infof("NodeAddresses(%v) => %v", name, addrs)
	return addrs, nil
}

// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (i *Instances) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
	instanceID, err := instanceIDFromProviderID(providerID)

	if err != nil {
		return []v1.NodeAddress{}, err
	}

	server, err := servers.Get(i.compute, instanceID).Extract()

	if err != nil {
		return []v1.NodeAddress{}, err
	}

	addresses, err := nodeAddresses(server)
	if err != nil {
		return []v1.NodeAddress{}, err
	}

	return addresses, nil
}

// ExternalID returns the cloud provider ID of the specified instance (deprecated).
func (i *Instances) ExternalID(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)
	if err != nil {
		if err == ErrNotFound {
			return "", cloudprovider.InstanceNotFound
		}
		return "", err
	}
	return srv.ID, nil
}

// InstanceExistsByProviderID returns true if the instance with the given provider id still exist.
// If false is returned with no error, the instance will be immediately deleted by the cloud controller manager.
func (i *Instances) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
	instanceID, err := instanceIDFromProviderID(providerID)
	if err != nil {
		return false, err
	}

	_, err = servers.Get(i.compute, instanceID).Extract()
	if err != nil {
		if isNotFound(err) {
			return false, nil
		}
		return false, err
	}

	return true, nil
}

// InstanceID returns the kubelet's cloud provider ID.
func (os *OpenStack) InstanceID() (string, error) {
	if len(os.localInstanceID) == 0 {
		id, err := readInstanceID(os.metadataOpts.SearchOrder)
		if err != nil {
			return "", err
		}
		os.localInstanceID = id
	}
	return os.localInstanceID, nil
}

// InstanceID returns the cloud provider ID of the specified instance.
func (i *Instances) InstanceID(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)
	if err != nil {
		if err == ErrNotFound {
			return "", cloudprovider.InstanceNotFound
		}
		return "", err
	}
	// In the future it is possible to also return an endpoint as:
	// <endpoint>/<instanceid>
	return "/" + srv.ID, nil
}

// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (i *Instances) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
	instanceID, err := instanceIDFromProviderID(providerID)

	if err != nil {
		return "", err
	}

	server, err := servers.Get(i.compute, instanceID).Extract()

	if err != nil {
		return "", err
	}

	return srvInstanceType(server)
}

// InstanceType returns the type of the specified instance.
func (i *Instances) InstanceType(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)

	if err != nil {
		return "", err
	}

	return srvInstanceType(srv)
}

func srvInstanceType(srv *servers.Server) (string, error) {
	keys := []string{"name", "id", "original_name"}
	for _, key := range keys {
		val, found := srv.Flavor[key]
		if found {
			flavor, ok := val.(string)
			if ok {
				return flavor, nil
			}
		}
	}
	return "", fmt.Errorf("flavor name/id not found")
}

// instanceIDFromProviderID splits a provider's id and return instanceID.
// A providerID is build out of '${ProviderName}:///${instance-id}'which contains ':///'.
// See cloudprovider.GetInstanceProviderID and Instances.InstanceID.
func instanceIDFromProviderID(providerID string) (instanceID string, err error) {
	// If Instances.InstanceID or cloudprovider.GetInstanceProviderID is changed, the regexp should be changed too.
	var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `:///([^/]+)$`)

	matches := providerIDRegexp.FindStringSubmatch(providerID)
	if len(matches) != 2 {
		return "", fmt.Errorf("ProviderID \"%s\" didn't match expected format \"openstack:///InstanceID\"", providerID)
	}
	return matches[1], nil
}
<|endoftext|>


<commit_before>// Copyright Project Harbor 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 dao

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/goharbor/harbor/src/jobservice/job"
	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/log"
	"github.com/goharbor/harbor/src/lib/orm"
	"github.com/goharbor/harbor/src/lib/q"
)

// ExecutionDAO is the data access object interface for execution
type ExecutionDAO interface {
	// Count returns the total count of executions according to the query
	// Query the "ExtraAttrs" by setting 'query.Keywords["ExtraAttrs.key"]="value"'
	Count(ctx context.Context, query *q.Query) (count int64, err error)
	// List the executions according to the query
	// Query the "ExtraAttrs" by setting 'query.Keywords["ExtraAttrs.key"]="value"'
	List(ctx context.Context, query *q.Query) (executions []*Execution, err error)
	// Get the specified execution
	Get(ctx context.Context, id int64) (execution *Execution, err error)
	// Create an execution
	Create(ctx context.Context, execution *Execution) (id int64, err error)
	// Update the specified execution. Only the properties specified by "props" will be updated if it is set
	Update(ctx context.Context, execution *Execution, props ...string) (err error)
	// Delete the specified execution
	Delete(ctx context.Context, id int64) (err error)
	// GetMetrics returns the task metrics for the specified execution
	GetMetrics(ctx context.Context, id int64) (metrics *Metrics, err error)
	// RefreshStatus refreshes the status of the specified execution according to it's tasks. If it's status
	// is final, update the end time as well
	// If the status is changed, the returning "statusChanged" is set as "true" and the current status indicates
	// the changed status
	RefreshStatus(ctx context.Context, id int64) (statusChanged bool, currentStatus string, err error)
}

// NewExecutionDAO returns an instance of ExecutionDAO
func NewExecutionDAO() ExecutionDAO {
	return &executionDAO{
		taskDAO: NewTaskDAO(),
	}
}

type executionDAO struct {
	taskDAO TaskDAO
}

func (e *executionDAO) Count(ctx context.Context, query *q.Query) (int64, error) {
	if query != nil {
		// ignore the page number and size
		query = &q.Query{
			Keywords: query.Keywords,
		}
	}
	qs, err := e.querySetter(ctx, query)
	if err != nil {
		return 0, err
	}
	return qs.Count()
}

func (e *executionDAO) List(ctx context.Context, query *q.Query) ([]*Execution, error) {
	executions := []*Execution{}
	qs, err := e.querySetter(ctx, query)
	if err != nil {
		return nil, err
	}
	if _, err = qs.All(&executions); err != nil {
		return nil, err
	}
	return executions, nil
}

func (e *executionDAO) Get(ctx context.Context, id int64) (*Execution, error) {
	execution := &Execution{
		ID: id,
	}
	ormer, err := orm.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	if err := ormer.Read(execution); err != nil {
		if e := orm.AsNotFoundError(err, "execution %d not found", id); e != nil {
			err = e
		}
		return nil, err
	}
	return execution, nil
}

func (e *executionDAO) Create(ctx context.Context, execution *Execution) (int64, error) {
	ormer, err := orm.FromContext(ctx)
	if err != nil {
		return 0, err
	}
	return ormer.Insert(execution)
}

func (e *executionDAO) Update(ctx context.Context, execution *Execution, props ...string) error {
	ormer, err := orm.FromContext(ctx)
	if err != nil {
		return err
	}
	n, err := ormer.Update(execution, props...)
	if err != nil {
		return err
	}
	if n == 0 {
		return errors.NotFoundError(nil).WithMessage("execution %d not found", execution.ID)
	}
	return nil
}

func (e *executionDAO) Delete(ctx context.Context, id int64) error {
	ormer, err := orm.FromContext(ctx)
	if err != nil {
		return err
	}
	n, err := ormer.Delete(&Execution{
		ID: id,
	})
	if err != nil {
		if e := orm.AsForeignKeyError(err,
			"the execution %d is referenced by other resources", id); e != nil {
			err = e
		}
		return err
	}
	if n == 0 {
		return errors.NotFoundError(nil).WithMessage("execution %d not found", id)
	}
	return nil
}

func (e *executionDAO) GetMetrics(ctx context.Context, id int64) (*Metrics, error) {
	scs, err := e.taskDAO.ListStatusCount(ctx, id)
	if err != nil {
		return nil, err
	}
	metrics := &Metrics{}
	if len(scs) == 0 {
		return metrics, nil
	}

	for _, sc := range scs {
		switch sc.Status {
		case job.SuccessStatus.String():
			metrics.SuccessTaskCount = sc.Count
		case job.ErrorStatus.String():
			metrics.ErrorTaskCount = sc.Count
		case job.PendingStatus.String():
			metrics.PendingTaskCount = sc.Count
		case job.RunningStatus.String():
			metrics.RunningTaskCount = sc.Count
		case job.ScheduledStatus.String():
			metrics.ScheduledTaskCount = sc.Count
		case job.StoppedStatus.String():
			metrics.StoppedTaskCount = sc.Count
		default:
			log.Errorf("unknown task status: %s", sc.Status)
		}
	}
	metrics.TaskCount = metrics.SuccessTaskCount + metrics.ErrorTaskCount +
		metrics.PendingTaskCount + metrics.RunningTaskCount +
		metrics.ScheduledTaskCount + metrics.StoppedTaskCount
	return metrics, nil
}

func (e *executionDAO) RefreshStatus(ctx context.Context, id int64) (bool, string, error) {
	// as the status of the execution can be refreshed by multiple operators concurrently
	// we use the optimistic locking to avoid the conflict and retry 5 times at most
	for i := 0; i < 5; i++ {
		statusChanged, currentStatus, retry, err := e.refreshStatus(ctx, id)
		if err != nil {
			return false, "", err
		}
		if !retry {
			return statusChanged, currentStatus, nil
		}
	}
	return false, "", fmt.Errorf("failed to refresh the status of the execution %d after %d retries", id, 5)
}

// the returning values:
// 1. bool: is the status changed
// 2. string: the current status if changed
// 3. bool: whether a retry is needed
// 4. error: the error
func (e *executionDAO) refreshStatus(ctx context.Context, id int64) (bool, string, bool, error) {
	execution, err := e.Get(ctx, id)
	if err != nil {
		return false, "", false, err
	}
	metrics, err := e.GetMetrics(ctx, id)
	if err != nil {
		return false, "", false, err
	}
	// no task, return directly
	if metrics.TaskCount == 0 {
		return false, "", false, nil
	}

	var status string
	if metrics.PendingTaskCount > 0 || metrics.RunningTaskCount > 0 || metrics.ScheduledTaskCount > 0 {
		status = job.RunningStatus.String()
	} else if metrics.ErrorTaskCount > 0 {
		status = job.ErrorStatus.String()
	} else if metrics.StoppedTaskCount > 0 {
		status = job.StoppedStatus.String()
	} else if metrics.SuccessTaskCount > 0 {
		status = job.SuccessStatus.String()
	}

	ormer, err := orm.FromContext(ctx)
	if err != nil {
		return false, "", false, err
	}

	sql := `update execution set status = ?, revision = revision+1, update_time = ? where id = ? and revision = ?`
	result, err := ormer.Raw(sql, status, time.Now(), id, execution.Revision).Exec()
	if err != nil {
		return false, "", false, err
	}
	n, err := result.RowsAffected()
	if err != nil {
		return false, "", false, err
	}

	// if the count of affected rows is 0, that means the execution is updating by others, retry
	if n == 0 {
		return false, "", true, nil
	}

	/* this is another solution to solve the concurrency issue for refreshing the execution status
	// set a score for each status:
	// 		pending, running, scheduled - 4
	// 		error - 3
	//		stopped - 2
	//		success - 1
	// and set the status of record with highest score as the status of execution
	sql := `with status_score as (
				select status,
					case
						when status='%s' or status='%s' or status='%s' then 4
						when status='%s' then 3
						when status='%s' then 2
						when status='%s' then 1
						else 0
					end as score
				from task
				where execution_id=?
				group by status
			)
			update execution
			set status=(
				select
					case
						when max(score)=4 then '%s'
						when max(score)=3 then '%s'
						when max(score)=2 then '%s'
						when max(score)=1 then '%s'
						when max(score)=0 then ''
					end as status
				from status_score)
			where id = ?`
	sql = fmt.Sprintf(sql, job.PendingStatus.String(), job.RunningStatus.String(), job.ScheduledStatus.String(),
		job.ErrorStatus.String(), job.StoppedStatus.String(), job.SuccessStatus.String(),
		job.RunningStatus.String(), job.ErrorStatus.String(), job.StoppedStatus.String(), job.SuccessStatus.String())
	if _, err = ormer.Raw(sql, id, id).Exec(); err != nil {
		return err
	}
	*/

	// update the end time if the status is final, otherwise set the end time as NULL, this is useful
	// for retrying jobs
	sql = `update execution
			set end_time = (
				case
					when status='%s' or status='%s' or status='%s' then  (
						select max(end_time)
						from task
						where execution_id=?)
					else NULL
				end)
			where id=?`
	sql = fmt.Sprintf(sql, job.ErrorStatus.String(), job.StoppedStatus.String(), job.SuccessStatus.String())
	_, err = ormer.Raw(sql, id, id).Exec()
	return status != execution.Status, status, false, err
}

func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.QuerySeter, error) {
	qs, err := orm.QuerySetter(ctx, &Execution{}, query)
	if err != nil {
		return nil, err
	}

	// append the filter for "extra attrs"
	if query != nil && len(query.Keywords) > 0 {
		var (
			key       string
			keyPrefix string
			value     interface{}
		)
		for key, value = range query.Keywords {
			if strings.HasPrefix(key, "ExtraAttrs.") {
				keyPrefix = "ExtraAttrs."
				break
			}
			if strings.HasPrefix(key, "extra_attrs.") {
				keyPrefix = "extra_attrs."
				break
			}
		}
		if len(keyPrefix) == 0 || keyPrefix == key {
			return qs, nil
		}

		// key with keyPrefix supports multi-level query operator on PostgreSQL JSON data
		// examples:
		// key = extra_attrs.id,
		//  ==> sql = "select id from execution where extra_attrs->>?=?", args = {id, value}
		// key = extra_attrs.artifact.digest
		//  ==> sql = "select id from execution where extra_attrs->?->>?=?", args = {artifact, id, value}
		// key = extra_attrs.a.b.c
		//  ==> sql = "select id from execution where extra_attrs->?->?->>?=?", args = {a, b, c, value}
		keys := strings.Split(strings.TrimPrefix(key, keyPrefix), ".")
		var args []interface{}
		for _, item := range keys {
			args = append(args, item)
		}
		args = append(args, value)
		inClause, err := orm.CreateInClause(ctx, buildInClauseSqlForExtraAttrs(keys), args...)
		if err != nil {
			return nil, err
		}
		qs = qs.FilterRaw("id", inClause)
	}

	return qs, nil
}

// Param keys is strings.Split() after trim "extra_attrs."/"ExtraAttrs." prefix
func buildInClauseSqlForExtraAttrs(keys []string) string {
	switch len(keys) {
	case 0:
		// won't fall into this case, as the if condition on "keyPrefix == key"
		// act as a place holder to ensure "default" is equivalent to "len(keys) >= 2"
		return ""
	case 1:
		return "select id from execution where extra_attrs->>?=?"
	default:
		// len(keys) >= 2
		elements := make([]string, len(keys)-1)
		for i := range elements {
			elements[i] = "?"
		}
		s := strings.Join(elements, "->")
		return fmt.Sprintf("select id from execution where extra_attrs->%s->>?=?", s)
	}
}
<commit_msg>Skip to refresh execution status when unchanged (#17023)<commit_after>//  Copyright Project Harbor 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 dao

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/goharbor/harbor/src/jobservice/job"
	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/log"
	"github.com/goharbor/harbor/src/lib/orm"
	"github.com/goharbor/harbor/src/lib/q"
)

// ExecutionDAO is the data access object interface for execution
type ExecutionDAO interface {
	// Count returns the total count of executions according to the query
	// Query the "ExtraAttrs" by setting 'query.Keywords["ExtraAttrs.key"]="value"'
	Count(ctx context.Context, query *q.Query) (count int64, err error)
	// List the executions according to the query
	// Query the "ExtraAttrs" by setting 'query.Keywords["ExtraAttrs.key"]="value"'
	List(ctx context.Context, query *q.Query) (executions []*Execution, err error)
	// Get the specified execution
	Get(ctx context.Context, id int64) (execution *Execution, err error)
	// Create an execution
	Create(ctx context.Context, execution *Execution) (id int64, err error)
	// Update the specified execution. Only the properties specified by "props" will be updated if it is set
	Update(ctx context.Context, execution *Execution, props ...string) (err error)
	// Delete the specified execution
	Delete(ctx context.Context, id int64) (err error)
	// GetMetrics returns the task metrics for the specified execution
	GetMetrics(ctx context.Context, id int64) (metrics *Metrics, err error)
	// RefreshStatus refreshes the status of the specified execution according to it's tasks. If it's status
	// is final, update the end time as well
	// If the status is changed, the returning "statusChanged" is set as "true" and the current status indicates
	// the changed status
	RefreshStatus(ctx context.Context, id int64) (statusChanged bool, currentStatus string, err error)
}

// NewExecutionDAO returns an instance of ExecutionDAO
func NewExecutionDAO() ExecutionDAO {
	return &executionDAO{
		taskDAO: NewTaskDAO(),
	}
}

type executionDAO struct {
	taskDAO TaskDAO
}

func (e *executionDAO) Count(ctx context.Context, query *q.Query) (int64, error) {
	if query != nil {
		// ignore the page number and size
		query = &q.Query{
			Keywords: query.Keywords,
		}
	}
	qs, err := e.querySetter(ctx, query)
	if err != nil {
		return 0, err
	}
	return qs.Count()
}

func (e *executionDAO) List(ctx context.Context, query *q.Query) ([]*Execution, error) {
	executions := []*Execution{}
	qs, err := e.querySetter(ctx, query)
	if err != nil {
		return nil, err
	}
	if _, err = qs.All(&executions); err != nil {
		return nil, err
	}
	return executions, nil
}

func (e *executionDAO) Get(ctx context.Context, id int64) (*Execution, error) {
	execution := &Execution{
		ID: id,
	}
	ormer, err := orm.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	if err := ormer.Read(execution); err != nil {
		if e := orm.AsNotFoundError(err, "execution %d not found", id); e != nil {
			err = e
		}
		return nil, err
	}
	return execution, nil
}

func (e *executionDAO) Create(ctx context.Context, execution *Execution) (int64, error) {
	ormer, err := orm.FromContext(ctx)
	if err != nil {
		return 0, err
	}
	return ormer.Insert(execution)
}

func (e *executionDAO) Update(ctx context.Context, execution *Execution, props ...string) error {
	ormer, err := orm.FromContext(ctx)
	if err != nil {
		return err
	}
	n, err := ormer.Update(execution, props...)
	if err != nil {
		return err
	}
	if n == 0 {
		return errors.NotFoundError(nil).WithMessage("execution %d not found", execution.ID)
	}
	return nil
}

func (e *executionDAO) Delete(ctx context.Context, id int64) error {
	ormer, err := orm.FromContext(ctx)
	if err != nil {
		return err
	}
	n, err := ormer.Delete(&Execution{
		ID: id,
	})
	if err != nil {
		if e := orm.AsForeignKeyError(err,
			"the execution %d is referenced by other resources", id); e != nil {
			err = e
		}
		return err
	}
	if n == 0 {
		return errors.NotFoundError(nil).WithMessage("execution %d not found", id)
	}
	return nil
}

func (e *executionDAO) GetMetrics(ctx context.Context, id int64) (*Metrics, error) {
	scs, err := e.taskDAO.ListStatusCount(ctx, id)
	if err != nil {
		return nil, err
	}
	metrics := &Metrics{}
	if len(scs) == 0 {
		return metrics, nil
	}

	for _, sc := range scs {
		switch sc.Status {
		case job.SuccessStatus.String():
			metrics.SuccessTaskCount = sc.Count
		case job.ErrorStatus.String():
			metrics.ErrorTaskCount = sc.Count
		case job.PendingStatus.String():
			metrics.PendingTaskCount = sc.Count
		case job.RunningStatus.String():
			metrics.RunningTaskCount = sc.Count
		case job.ScheduledStatus.String():
			metrics.ScheduledTaskCount = sc.Count
		case job.StoppedStatus.String():
			metrics.StoppedTaskCount = sc.Count
		default:
			log.Errorf("unknown task status: %s", sc.Status)
		}
	}
	metrics.TaskCount = metrics.SuccessTaskCount + metrics.ErrorTaskCount +
		metrics.PendingTaskCount + metrics.RunningTaskCount +
		metrics.ScheduledTaskCount + metrics.StoppedTaskCount
	return metrics, nil
}

func (e *executionDAO) RefreshStatus(ctx context.Context, id int64) (bool, string, error) {
	// as the status of the execution can be refreshed by multiple operators concurrently
	// we use the optimistic locking to avoid the conflict and retry 5 times at most
	for i := 0; i < 5; i++ {
		statusChanged, currentStatus, retry, err := e.refreshStatus(ctx, id)
		if err != nil {
			return false, "", err
		}
		if !retry {
			return statusChanged, currentStatus, nil
		}
	}
	return false, "", fmt.Errorf("failed to refresh the status of the execution %d after %d retries", id, 5)
}

// the returning values:
// 1. bool: is the status changed
// 2. string: the current status if changed
// 3. bool: whether a retry is needed
// 4. error: the error
func (e *executionDAO) refreshStatus(ctx context.Context, id int64) (bool, string, bool, error) {
	metrics, err := e.GetMetrics(ctx, id)
	if err != nil {
		return false, "", false, err
	}
	// no task, return directly
	if metrics.TaskCount == 0 {
		return false, "", false, nil
	}

	var status string
	if metrics.PendingTaskCount > 0 || metrics.RunningTaskCount > 0 || metrics.ScheduledTaskCount > 0 {
		status = job.RunningStatus.String()
	} else if metrics.ErrorTaskCount > 0 {
		status = job.ErrorStatus.String()
	} else if metrics.StoppedTaskCount > 0 {
		status = job.StoppedStatus.String()
	} else if metrics.SuccessTaskCount > 0 {
		status = job.SuccessStatus.String()
	}

	execution, err := e.Get(ctx, id)
	if err != nil {
		return false, "", false, err
	}
	if status == execution.Status {
		return false, status, false, nil // status not changed
	}

	ormer, err := orm.FromContext(ctx)
	if err != nil {
		return false, "", false, err
	}

	sql := `update execution set status = ?, revision = revision+1, update_time = ? where id = ? and revision = ?`
	result, err := ormer.Raw(sql, status, time.Now(), id, execution.Revision).Exec()
	if err != nil {
		return false, "", false, err
	}
	n, err := result.RowsAffected()
	if err != nil {
		return false, "", false, err
	}

	// if the count of affected rows is 0, that means the execution is updating by others, retry
	if n == 0 {
		return false, "", true, nil
	}

	/* this is another solution to solve the concurrency issue for refreshing the execution status
	// set a score for each status:
	// 		pending, running, scheduled - 4
	// 		error - 3
	//		stopped - 2
	//		success - 1
	// and set the status of record with highest score as the status of execution
	sql := `with status_score as (
				select status,
					case
						when status='%s' or status='%s' or status='%s' then 4
						when status='%s' then 3
						when status='%s' then 2
						when status='%s' then 1
						else 0
					end as score
				from task
				where execution_id=?
				group by status
			)
			update execution
			set status=(
				select
					case
						when max(score)=4 then '%s'
						when max(score)=3 then '%s'
						when max(score)=2 then '%s'
						when max(score)=1 then '%s'
						when max(score)=0 then ''
					end as status
				from status_score)
			where id = ?`
	sql = fmt.Sprintf(sql, job.PendingStatus.String(), job.RunningStatus.String(), job.ScheduledStatus.String(),
		job.ErrorStatus.String(), job.StoppedStatus.String(), job.SuccessStatus.String(),
		job.RunningStatus.String(), job.ErrorStatus.String(), job.StoppedStatus.String(), job.SuccessStatus.String())
	if _, err = ormer.Raw(sql, id, id).Exec(); err != nil {
		return err
	}
	*/

	// update the end time if the status is final, otherwise set the end time as NULL, this is useful
	// for retrying jobs
	sql = `update execution
			set end_time = (
				case
					when status='%s' or status='%s' or status='%s' then  (
						select max(end_time)
						from task
						where execution_id=?)
					else NULL
				end)
			where id=?`
	sql = fmt.Sprintf(sql, job.ErrorStatus.String(), job.StoppedStatus.String(), job.SuccessStatus.String())
	_, err = ormer.Raw(sql, id, id).Exec()
	return status != execution.Status, status, false, err
}

func (e *executionDAO) querySetter(ctx context.Context, query *q.Query) (orm.QuerySeter, error) {
	qs, err := orm.QuerySetter(ctx, &Execution{}, query)
	if err != nil {
		return nil, err
	}

	// append the filter for "extra attrs"
	if query != nil && len(query.Keywords) > 0 {
		var (
			key       string
			keyPrefix string
			value     interface{}
		)
		for key, value = range query.Keywords {
			if strings.HasPrefix(key, "ExtraAttrs.") {
				keyPrefix = "ExtraAttrs."
				break
			}
			if strings.HasPrefix(key, "extra_attrs.") {
				keyPrefix = "extra_attrs."
				break
			}
		}
		if len(keyPrefix) == 0 || keyPrefix == key {
			return qs, nil
		}

		// key with keyPrefix supports multi-level query operator on PostgreSQL JSON data
		// examples:
		// key = extra_attrs.id,
		//  ==> sql = "select id from execution where extra_attrs->>?=?", args = {id, value}
		// key = extra_attrs.artifact.digest
		//  ==> sql = "select id from execution where extra_attrs->?->>?=?", args = {artifact, id, value}
		// key = extra_attrs.a.b.c
		//  ==> sql = "select id from execution where extra_attrs->?->?->>?=?", args = {a, b, c, value}
		keys := strings.Split(strings.TrimPrefix(key, keyPrefix), ".")
		var args []interface{}
		for _, item := range keys {
			args = append(args, item)
		}
		args = append(args, value)
		inClause, err := orm.CreateInClause(ctx, buildInClauseSqlForExtraAttrs(keys), args...)
		if err != nil {
			return nil, err
		}
		qs = qs.FilterRaw("id", inClause)
	}

	return qs, nil
}

// Param keys is strings.Split() after trim "extra_attrs."/"ExtraAttrs." prefix
func buildInClauseSqlForExtraAttrs(keys []string) string {
	switch len(keys) {
	case 0:
		// won't fall into this case, as the if condition on "keyPrefix == key"
		// act as a place holder to ensure "default" is equivalent to "len(keys) >= 2"
		return ""
	case 1:
		return "select id from execution where extra_attrs->>?=?"
	default:
		// len(keys) >= 2
		elements := make([]string, len(keys)-1)
		for i := range elements {
			elements[i] = "?"
		}
		s := strings.Join(elements, "->")
		return fmt.Sprintf("select id from execution where extra_attrs->%s->>?=?", s)
	}
}
<|endoftext|>


<commit_before>// Copyright "2"014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file contains the frame primitive functions.

package golisp

import (
	"fmt"
)

func RegisterFramePrimitives() {
	MakePrimitiveFunction("make-frame", "*", MakeFrameImpl)
	MakePrimitiveFunction("has-slot?", "2", HasSlotImpl)
	MakePrimitiveFunction("get-slot", "2", GetSlotImpl)
	MakePrimitiveFunction("get-slot-or-nil", "2", GetSlotOrNilImpl)
	MakePrimitiveFunction("remove-slot!", "2", RemoveSlotImpl)
	MakePrimitiveFunction("set-slot!", "3", SetSlotImpl)
	MakePrimitiveFunction("send", ">=2", SendImpl)
	MakePrimitiveFunction("send-super", ">=1", SendSuperImpl)
	MakePrimitiveFunction("clone", "1", CloneImpl)
	MakePrimitiveFunction("json->lisp", "1", JsonToLispImpl)
	MakePrimitiveFunction("lisp->json", "1", LispToJsonImpl)
	MakePrimitiveFunction("frame-keys", "1", FrameKeysImpl)
	MakePrimitiveFunction("frame-values", "1", FrameValuesImpl)
}

func MakeFrameImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args)%2 != 0 {
		err = ProcessError("Frames must be initialized with an even number of arguments.", env)
	}
	m := make(FrameMap)
	for c := args; NotNilP(c); c = Cddr(c) {
		k := Car(c)
		if !NakedP(k) {
			err = ProcessError(fmt.Sprintf("Frame keys must be naked symbols, but was given %s.", String(k)), env)
			return
		}
		v := Cadr(c)
		m[StringValue(k)] = v
	}
	return FrameWithValue(&m), nil
}

func HasSlotImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("has-slot? requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	k := Cadr(args)
	if !SymbolP(k) {
		err = ProcessError(fmt.Sprintf("has-slot? requires a symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	return BooleanWithValue(FrameValue(f).HasSlot(StringValue(k))), nil
}

func GetSlotImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("get-slot requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	if FrameValue(f) == nil {
		err = ProcessError("get-slot received a nil frame.", env)
		return
	}

	k := Cadr(args)
	if !SymbolP(k) {
		err = ProcessError(fmt.Sprintf("get-slot requires a symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	if !FrameValue(f).HasSlot(StringValue(k)) {
		err = ProcessError(fmt.Sprintf("get-slot requires an existing slot, but was given %s.", String(k)), env)
		return
	}

	return FrameValue(f).Get(StringValue(k)), nil
}

func GetSlotOrNilImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("get-slot-or-nil requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	k := Cadr(args)
	if !SymbolP(k) {
		err = ProcessError(fmt.Sprintf("get-slot-or-nil requires a symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	return FrameValue(f).Get(StringValue(k)), nil
}

func RemoveSlotImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)

	if NilP(f) {
		return LispFalse, nil
	}

	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("remove-slot! requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	k := Cadr(args)
	if !SymbolP(k) {
		err = ProcessError(fmt.Sprintf("remove-slot! requires a symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	return BooleanWithValue(FrameValue(f).Remove(StringValue(k))), nil
}

func SetSlotImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("set-slot! requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	k := Cadr(args)
	if !NakedP(k) {
		err = ProcessError(fmt.Sprintf("set-slot! requires a naked symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	v := Caddr(args)

	return FrameValue(f).Set(StringValue(k), v), nil
}

func SendImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("send requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	k := Cadr(args)
	if !SymbolP(k) {
		err = ProcessError(fmt.Sprintf("send requires a symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	if !FrameValue(f).HasSlot(StringValue(k)) {
		err = ProcessError(fmt.Sprintf("send requires an existing slot, but was given %s.", String(k)), env)
		return
	}

	fun := FrameValue(f).Get(StringValue(k))
	if !FunctionP(fun) {
		err = ProcessError(fmt.Sprintf("send requires a function slot, but was given a slot containing a %s.", TypeName(TypeOf(fun))), env)
		return
	}

	params := Cddr(args)
	return FunctionValue(fun).ApplyWithoutEvalWithFrame(params, env, FrameValue(f))
}

func getSuperFunction(selector string, env *SymbolTableFrame) *Data {
	f := env.Frame
	if f == nil {
		return nil
	}

	for _, p := range f.Parents() {
		fun := p.Get(selector)
		if fun != nil {
			return fun
		}
	}

	return nil
}

func SendSuperImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if !env.HasFrame() {
		err = ProcessError("send-super can only be used within the context of a frame.", env)
		return
	}

	selector := Car(args)
	if !SymbolP(selector) {
		err = ProcessError(fmt.Sprintf("Selector must be a symbol but was %s.", TypeName(TypeOf(selector))), env)
		return
	}

	fun := getSuperFunction(StringValue(selector), env)
	if fun == nil || !FunctionP(fun) {
		err = ProcessError(fmt.Sprintf("Message sent must select a function slot but was %s.", TypeName(TypeOf(fun))), env)
		return
	}

	params := Cdr(args)
	frameEnv := NewSymbolTableFrameBelowWithFrame(env, env.Frame, fmt.Sprintf("%s'", env.Name))
	frameEnv.BindLocallyTo(Intern("self"), FrameWithValue(env.Frame))
	return FunctionValue(fun).ApplyWithoutEval(params, frameEnv)
}

func CloneImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("clone requires a frame as it's argument, but was given %s.", String(f)), env)
		return
	}

	return FrameWithValue(FrameValue(f).Clone()), nil
}

func JsonToLispImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	j := Car(args)
	if !StringP(j) {
		err = ProcessError(fmt.Sprintf("json->lisp requires a string as it's argument, but was given %s.", String(j)), env)
		return
	}

	return JsonStringToLispWithFrames(StringValue(j)), nil
}

func LispToJsonImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	l := Car(args)

	return StringWithValue(LispWithFramesToJsonString(l)), nil
}

func FrameKeysImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("keys requires a frame as it's argument, but was given %s.", String(f)), env)
		return
	}

	return ArrayToList(FrameValue(f).Keys()), nil
}

func FrameValuesImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("vals requires a frame as it's argument, but was given %s.", String(f)), env)
		return
	}

	return ArrayToList(FrameValue(f).Values()), nil
}
<commit_msg>Tweka & bug fix<commit_after>// Copyright "2"014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file contains the frame primitive functions.

package golisp

import (
	"fmt"
)

func RegisterFramePrimitives() {
	MakePrimitiveFunction("make-frame", "*", MakeFrameImpl)
	MakePrimitiveFunction("has-slot?", "2", HasSlotImpl)
	MakePrimitiveFunction("get-slot", "2", GetSlotImpl)
	MakePrimitiveFunction("get-slot-or-nil", "2", GetSlotOrNilImpl)
	MakePrimitiveFunction("remove-slot!", "2", RemoveSlotImpl)
	MakePrimitiveFunction("set-slot!", "3", SetSlotImpl)
	MakePrimitiveFunction("send", ">=2", SendImpl)
	MakePrimitiveFunction("send-super", ">=1", SendSuperImpl)
	MakePrimitiveFunction("clone", "1", CloneImpl)
	MakePrimitiveFunction("json->lisp", "1", JsonToLispImpl)
	MakePrimitiveFunction("lisp->json", "1", LispToJsonImpl)
	MakePrimitiveFunction("frame-keys", "1", FrameKeysImpl)
	MakePrimitiveFunction("frame-values", "1", FrameValuesImpl)
}

func MakeFrameImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if Length(args)%2 != 0 {
		err = ProcessError("Frames must be initialized with an even number of arguments.", env)
		return
	}
	m := make(FrameMap)
	for c := args; NotNilP(c); c = Cddr(c) {
		k := Car(c)
		if !NakedP(k) {
			err = ProcessError(fmt.Sprintf("Frame keys must be naked symbols, but was given %s.", String(k)), env)
			return
		}
		v := Cadr(c)
		m[StringValue(k)] = v
	}
	return FrameWithValue(&m), nil
}

func HasSlotImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("has-slot? requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	k := Cadr(args)
	if !NakedP(k) {
		err = ProcessError(fmt.Sprintf("has-slot? requires a naked symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	return BooleanWithValue(FrameValue(f).HasSlot(StringValue(k))), nil
}

func GetSlotImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("get-slot requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	if FrameValue(f) == nil {
		err = ProcessError("get-slot received a nil frame.", env)
		return
	}

	k := Cadr(args)
	if !SymbolP(k) {
		err = ProcessError(fmt.Sprintf("get-slot requires a symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	if !FrameValue(f).HasSlot(StringValue(k)) {
		err = ProcessError(fmt.Sprintf("get-slot requires an existing slot, but was given %s.", String(k)), env)
		return
	}

	return FrameValue(f).Get(StringValue(k)), nil
}

func GetSlotOrNilImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("get-slot-or-nil requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	k := Cadr(args)
	if !SymbolP(k) {
		err = ProcessError(fmt.Sprintf("get-slot-or-nil requires a symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	return FrameValue(f).Get(StringValue(k)), nil
}

func RemoveSlotImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)

	if NilP(f) {
		return LispFalse, nil
	}

	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("remove-slot! requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	k := Cadr(args)
	if !SymbolP(k) {
		err = ProcessError(fmt.Sprintf("remove-slot! requires a symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	return BooleanWithValue(FrameValue(f).Remove(StringValue(k))), nil
}

func SetSlotImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("set-slot! requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	k := Cadr(args)
	if !NakedP(k) {
		err = ProcessError(fmt.Sprintf("set-slot! requires a naked symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	v := Caddr(args)

	return FrameValue(f).Set(StringValue(k), v), nil
}

func SendImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("send requires a frame as it's first argument, but was given %s.", String(f)), env)
		return
	}

	k := Cadr(args)
	if !SymbolP(k) {
		err = ProcessError(fmt.Sprintf("send requires a symbol as it's second argument, but was given %s.", String(k)), env)
		return
	}

	if !FrameValue(f).HasSlot(StringValue(k)) {
		err = ProcessError(fmt.Sprintf("send requires an existing slot, but was given %s.", String(k)), env)
		return
	}

	fun := FrameValue(f).Get(StringValue(k))
	if !FunctionP(fun) {
		err = ProcessError(fmt.Sprintf("send requires a function slot, but was given a slot containing a %s.", TypeName(TypeOf(fun))), env)
		return
	}

	params := Cddr(args)
	return FunctionValue(fun).ApplyWithoutEvalWithFrame(params, env, FrameValue(f))
}

func getSuperFunction(selector string, env *SymbolTableFrame) *Data {
	f := env.Frame
	if f == nil {
		return nil
	}

	for _, p := range f.Parents() {
		fun := p.Get(selector)
		if fun != nil {
			return fun
		}
	}

	return nil
}

func SendSuperImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	if !env.HasFrame() {
		err = ProcessError("send-super can only be used within the context of a frame.", env)
		return
	}

	selector := Car(args)
	if !SymbolP(selector) {
		err = ProcessError(fmt.Sprintf("Selector must be a symbol but was %s.", TypeName(TypeOf(selector))), env)
		return
	}

	fun := getSuperFunction(StringValue(selector), env)
	if fun == nil || !FunctionP(fun) {
		err = ProcessError(fmt.Sprintf("Message sent must select a function slot but was %s.", TypeName(TypeOf(fun))), env)
		return
	}

	params := Cdr(args)
	frameEnv := NewSymbolTableFrameBelowWithFrame(env, env.Frame, fmt.Sprintf("%s'", env.Name))
	frameEnv.BindLocallyTo(Intern("self"), FrameWithValue(env.Frame))
	return FunctionValue(fun).ApplyWithoutEval(params, frameEnv)
}

func CloneImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("clone requires a frame as it's argument, but was given %s.", String(f)), env)
		return
	}

	return FrameWithValue(FrameValue(f).Clone()), nil
}

func JsonToLispImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	j := Car(args)
	if !StringP(j) {
		err = ProcessError(fmt.Sprintf("json->lisp requires a string as it's argument, but was given %s.", String(j)), env)
		return
	}

	return JsonStringToLispWithFrames(StringValue(j)), nil
}

func LispToJsonImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	l := Car(args)

	return StringWithValue(LispWithFramesToJsonString(l)), nil
}

func FrameKeysImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("keys requires a frame as it's argument, but was given %s.", String(f)), env)
		return
	}

	return ArrayToList(FrameValue(f).Keys()), nil
}

func FrameValuesImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
	f := Car(args)
	if !FrameP(f) {
		err = ProcessError(fmt.Sprintf("vals requires a frame as it's argument, but was given %s.", String(f)), env)
		return
	}

	return ArrayToList(FrameValue(f).Values()), nil
}
<|endoftext|>