<commit_before>// Copyright 2017-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Command pxeboot implements PXE-based booting.
//
// pxeboot combines a DHCP client with a TFTP/HTTP client to download files as
// well as pxelinux and iPXE configuration file parsing.
//
// PXE-based booting requests a DHCP lease, and looks at the BootFileName and
// ServerName options (which may be embedded in the original BOOTP message, or
// as option codes) to find something to boot.
//
// This BootFileName may point to
//
// - an iPXE script beginning with #!ipxe
//
// - a pxelinux.0, in which case we will ignore the pxelinux and try to parse
//   pxelinux.cfg/<files>
package main

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

	"github.com/u-root/u-root/pkg/boot"
	"github.com/u-root/u-root/pkg/boot/menu"
	"github.com/u-root/u-root/pkg/boot/netboot"
	"github.com/u-root/u-root/pkg/curl"
	"github.com/u-root/u-root/pkg/dhclient"
	"github.com/u-root/u-root/pkg/ulog"
)

var (
	ifName  = "^e.*"
	noLoad  = flag.Bool("no-load", false, "get DHCP response, but don't load the kernel")
	dryRun  = flag.Bool("dry-run", false, "download kernel, but don't kexec it")
	verbose = flag.Bool("v", false, "Verbose output")
)

const (
	dhcpTimeout = 5 * time.Second
	dhcpTries   = 3
)

// NetbootImages requests DHCP on every ifaceNames interface, and parses
// netboot images from the DHCP leases. Returns bootable OSes.
func NetbootImages(ifaceNames string) ([]boot.OSImage, error) {
	filteredIfs, err := dhclient.Interfaces(ifaceNames)
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), (1<<dhcpTries)*dhcpTimeout)
	defer cancel()

	c := dhclient.Config{
		Timeout: dhcpTimeout,
		Retries: dhcpTries,
	}
	if *verbose {
		c.LogLevel = dhclient.LogSummary
	}
	r := dhclient.SendRequests(ctx, filteredIfs, true, true, c)

	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()

		case result, ok := <-r:
			if !ok {
				log.Printf("Configured all interfaces.")
				return nil, fmt.Errorf("nothing bootable found")
			}
			if result.Err != nil {
				continue
			}

			if err := result.Lease.Configure(); err != nil {
				log.Printf("Failed to configure lease %s: %v", result.Lease, err)
				// Boot further regardless of lease configuration result.
				//
				// If lease failed, fall back to use locally configured
				// ip/ipv6 address.
			}

			// Don't use the other context, as it's for the DHCP timeout.
			imgs, err := netboot.BootImages(context.Background(), ulog.Log, curl.DefaultSchemes, result.Lease)
			if err != nil {
				log.Printf("Failed to boot lease %v: %v", result.Lease, err)
				continue
			}
			return imgs, nil
		}
	}
}

func main() {
	flag.Parse()
	if len(flag.Args()) > 1 {
		log.Fatalf("Only one regexp-style argument is allowed, e.g.: " + ifName)
	}

	if len(flag.Args()) > 0 {
		ifName = flag.Args()[0]
	}

	images, err := NetbootImages(ifName)
	if err != nil {
		log.Fatal(err)
	}

	if *noLoad {
		log.Printf("Got configuration: %s", images[0])
		return
	}
	menuEntries := menu.OSImages(*dryRun, images...)
	menuEntries = append(menuEntries, menu.Reboot{})
	menuEntries = append(menuEntries, menu.StartShell{})

	menu.ShowMenuAndBoot(os.Stdin, menuEntries...)

	// Kexec should either return an error or not return.
	panic("unreachable")

}
<commit_msg>pxeboot: still show boot menu when no netboot kernels are found<commit_after>// Copyright 2017-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Command pxeboot implements PXE-based booting.
//
// pxeboot combines a DHCP client with a TFTP/HTTP client to download files as
// well as pxelinux and iPXE configuration file parsing.
//
// PXE-based booting requests a DHCP lease, and looks at the BootFileName and
// ServerName options (which may be embedded in the original BOOTP message, or
// as option codes) to find something to boot.
//
// This BootFileName may point to
//
// - an iPXE script beginning with #!ipxe
//
// - a pxelinux.0, in which case we will ignore the pxelinux and try to parse
//   pxelinux.cfg/<files>
package main

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

	"github.com/u-root/u-root/pkg/boot"
	"github.com/u-root/u-root/pkg/boot/menu"
	"github.com/u-root/u-root/pkg/boot/netboot"
	"github.com/u-root/u-root/pkg/curl"
	"github.com/u-root/u-root/pkg/dhclient"
	"github.com/u-root/u-root/pkg/ulog"
)

var (
	ifName  = "^e.*"
	noLoad  = flag.Bool("no-load", false, "get DHCP response, but don't load the kernel")
	dryRun  = flag.Bool("dry-run", false, "download kernel, but don't kexec it")
	verbose = flag.Bool("v", false, "Verbose output")
)

const (
	dhcpTimeout = 5 * time.Second
	dhcpTries   = 3
)

// NetbootImages requests DHCP on every ifaceNames interface, and parses
// netboot images from the DHCP leases. Returns bootable OSes.
func NetbootImages(ifaceNames string) ([]boot.OSImage, error) {
	filteredIfs, err := dhclient.Interfaces(ifaceNames)
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), (1<<dhcpTries)*dhcpTimeout)
	defer cancel()

	c := dhclient.Config{
		Timeout: dhcpTimeout,
		Retries: dhcpTries,
	}
	if *verbose {
		c.LogLevel = dhclient.LogSummary
	}
	r := dhclient.SendRequests(ctx, filteredIfs, true, true, c)

	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()

		case result, ok := <-r:
			if !ok {
				log.Printf("Configured all interfaces.")
				return nil, fmt.Errorf("nothing bootable found")
			}
			if result.Err != nil {
				continue
			}

			if err := result.Lease.Configure(); err != nil {
				log.Printf("Failed to configure lease %s: %v", result.Lease, err)
				// Boot further regardless of lease configuration result.
				//
				// If lease failed, fall back to use locally configured
				// ip/ipv6 address.
			}

			// Don't use the other context, as it's for the DHCP timeout.
			imgs, err := netboot.BootImages(context.Background(), ulog.Log, curl.DefaultSchemes, result.Lease)
			if err != nil {
				log.Printf("Failed to boot lease %v: %v", result.Lease, err)
				continue
			}
			return imgs, nil
		}
	}
}

func main() {
	flag.Parse()
	if len(flag.Args()) > 1 {
		log.Fatalf("Only one regexp-style argument is allowed, e.g.: " + ifName)
	}

	if len(flag.Args()) > 0 {
		ifName = flag.Args()[0]
	}

	images, err := NetbootImages(ifName)
	if err != nil {
		log.Printf("Netboot failed: %v", err)
	}

	if *noLoad {
		if len(images) > 0 {
			log.Printf("Got configuration: %s", images[0])
		} else {
			log.Fatalf("Nothing bootable found.")
		}
		return
	}
	menuEntries := menu.OSImages(*dryRun, images...)
	menuEntries = append(menuEntries, menu.Reboot{})
	menuEntries = append(menuEntries, menu.StartShell{})

	menu.ShowMenuAndBoot(os.Stdin, menuEntries...)

	// Kexec should either return an error or not return.
	panic("unreachable")

}
<|endoftext|>


<commit_before>package rest

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulse"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulseprofile"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestPublicPulseProfile(t *testing.T) {
	t.Run("missing arguments", func(t *testing.T) {
		c := NewClient()
		pp, err := c.Pulse.PublicPulseProfile("")
		require.NotNil(t, err)
		require.Nil(t, pp)
	})

	t.Run("response data slice too short", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			respData := []interface{}{"abc123"}
			payload, _ := json.Marshal(respData)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pp, err := c.Pulse.PublicPulseProfile("Bitfinex")
		require.NotNil(t, err)
		require.Nil(t, pp)
	})

	t.Run("valid response data", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			respData := []interface{}{
				"abc123",
				float64(1591614631576),
				nil,
				"nickname",
				nil,
				"picture",
				"text",
				nil,
				nil,
				"twitter",
				nil,
				30,
				5,
				nil,
			}
			payload, _ := json.Marshal(respData)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pp, err := c.Pulse.PublicPulseProfile("Bitfinex")
		require.Nil(t, err)

		expected := &pulseprofile.PulseProfile{
			ID:            "abc123",
			MTS:           1591614631576,
			Nickname:      "nickname",
			Picture:       "picture",
			Text:          "text",
			TwitterHandle: "twitter",
		}
		assert.Equal(t, expected, pp)
	})
}

func TestPublicPulseHistory(t *testing.T) {
	t.Run("response data slice too short", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			respData := []interface{}{
				[]interface{}{"id"},
			}
			payload, _ := json.Marshal(respData)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pp, err := c.Pulse.PublicPulseHistory("", "")
		require.NotNil(t, err)
		require.Nil(t, pp)
	})

	t.Run("valid response data no profile", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			respData := []interface{}{
				[]interface{}{
					"id",
					float64(1591614631576),
					nil,
					"uid",
					nil,
					"title",
					"content",
					nil,
					nil,
					1,
					1,
					nil,
					[]interface{}{"tag1", "tag2"},
					[]interface{}{"attach1", "attach2"},
					nil,
					5,
					nil,
					nil,
					nil,
				},
			}
			payload, _ := json.Marshal(respData)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pph, err := c.Pulse.PublicPulseHistory("", "")
		require.Nil(t, err)

		expected := &pulse.Pulse{
			ID:          "id",
			MTS:         1591614631576,
			UserID:      "uid",
			Title:       "title",
			Content:     "content",
			IsPin:       1,
			IsPublic:    1,
			Tags:        []string{"tag1", "tag2"},
			Attachments: []string{"attach1", "attach2"},
			Likes:       5,
		}

		assert.Equal(t, expected, pph[0])
	})
}

func TestAddPulse(t *testing.T) {
	t.Run("invalid payload", func(t *testing.T) {
		p := &pulse.Pulse{Title: "foo"}
		c := NewClient()
		pm, err := c.Pulse.AddPulse(p)
		require.NotNil(t, err)
		require.Nil(t, pm)
	})

	t.Run("valid payload", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			respData := []interface{}{
				"id",
				float64(1591614631576),
				nil,
				"uid",
				nil,
				"title",
				"content",
				nil,
				nil,
				1,
				1,
				nil,
				[]interface{}{"tag1", "tag2"},
				[]interface{}{"attach1", "attach2"},
				nil,
				5,
				nil,
				nil,
				nil,
			}
			payload, _ := json.Marshal(respData)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pm, err := c.Pulse.AddPulse(&pulse.Pulse{Title: "foo bar baz qux 123"})
		require.Nil(t, err)

		expected := &pulse.Pulse{
			ID:          "id",
			MTS:         1591614631576,
			UserID:      "uid",
			Title:       "title",
			Content:     "content",
			IsPin:       1,
			IsPublic:    1,
			Tags:        []string{"tag1", "tag2"},
			Attachments: []string{"attach1", "attach2"},
			Likes:       5,
		}

		assert.Equal(t, expected, pm)
	})
}
<commit_msg>new PulseHistory function test coverage<commit_after>package rest

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulse"
	"github.com/bitfinexcom/bitfinex-api-go/pkg/models/pulseprofile"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestPublicPulseProfile(t *testing.T) {
	t.Run("missing arguments", func(t *testing.T) {
		c := NewClient()
		pp, err := c.Pulse.PublicPulseProfile("")
		require.NotNil(t, err)
		require.Nil(t, pp)
	})

	t.Run("response data slice too short", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			respMock := []interface{}{"abc123"}
			payload, _ := json.Marshal(respMock)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pp, err := c.Pulse.PublicPulseProfile("Bitfinex")
		require.NotNil(t, err)
		require.Nil(t, pp)
	})

	t.Run("valid response data", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			respMock := []interface{}{
				"abc123",
				float64(1591614631576),
				nil,
				"nickname",
				nil,
				"picture",
				"text",
				nil,
				nil,
				"twitter",
				nil,
				30,
				5,
				nil,
			}
			payload, _ := json.Marshal(respMock)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pp, err := c.Pulse.PublicPulseProfile("Bitfinex")
		require.Nil(t, err)

		expected := &pulseprofile.PulseProfile{
			ID:            "abc123",
			MTS:           1591614631576,
			Nickname:      "nickname",
			Picture:       "picture",
			Text:          "text",
			TwitterHandle: "twitter",
		}
		assert.Equal(t, expected, pp)
	})
}

func TestPublicPulseHistory(t *testing.T) {
	t.Run("response data slice too short", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			respMock := []interface{}{
				[]interface{}{"id"},
			}
			payload, _ := json.Marshal(respMock)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pp, err := c.Pulse.PublicPulseHistory(1, 0)
		require.NotNil(t, err)
		require.Nil(t, pp)
	})

	t.Run("valid response data no profile", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			limit := r.URL.Query().Get("limit")
			end := r.URL.Query().Get("end")
			assert.Equal(t, "1", limit)
			assert.Equal(t, "1591691528075", end)

			respMock := []interface{}{
				[]interface{}{
					"id",
					float64(1591614631576),
					nil,
					"uid",
					nil,
					"title",
					"content",
					nil,
					nil,
					1,
					1,
					nil,
					[]interface{}{"tag1", "tag2"},
					[]interface{}{"attach1", "attach2"},
					nil,
					5,
					nil,
					nil,
					nil,
				},
			}
			payload, _ := json.Marshal(respMock)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pph, err := c.Pulse.PublicPulseHistory(1, 1591691528075)
		require.Nil(t, err)

		expected := &pulse.Pulse{
			ID:          "id",
			MTS:         1591614631576,
			UserID:      "uid",
			Title:       "title",
			Content:     "content",
			IsPin:       1,
			IsPublic:    1,
			Tags:        []string{"tag1", "tag2"},
			Attachments: []string{"attach1", "attach2"},
			Likes:       5,
		}

		assert.Equal(t, expected, pph[0])
	})
}

func TestAddPulse(t *testing.T) {
	t.Run("invalid payload", func(t *testing.T) {
		p := &pulse.Pulse{Title: "foo"}
		c := NewClient()
		pm, err := c.Pulse.AddPulse(p)
		require.NotNil(t, err)
		require.Nil(t, pm)
	})

	t.Run("response data slice too short", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			respMock := []interface{}{"id"}
			payload, _ := json.Marshal(respMock)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pm, err := c.Pulse.AddPulse(&pulse.Pulse{Title: "foo bar baz qux 123"})
		require.NotNil(t, err)
		require.Nil(t, pm)
	})

	t.Run("valid payload", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			respMock := []interface{}{
				"id",
				float64(1591614631576),
				nil,
				"uid",
				nil,
				"title",
				"content",
				nil,
				nil,
				1,
				1,
				nil,
				[]interface{}{"tag1", "tag2"},
				[]interface{}{"attach1", "attach2"},
				nil,
				5,
				nil,
				nil,
				nil,
			}
			payload, _ := json.Marshal(respMock)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pm, err := c.Pulse.AddPulse(&pulse.Pulse{Title: "foo bar baz qux 123"})
		require.Nil(t, err)

		expected := &pulse.Pulse{
			ID:          "id",
			MTS:         1591614631576,
			UserID:      "uid",
			Title:       "title",
			Content:     "content",
			IsPin:       1,
			IsPublic:    1,
			Tags:        []string{"tag1", "tag2"},
			Attachments: []string{"attach1", "attach2"},
			Likes:       5,
		}

		assert.Equal(t, expected, pm)
	})
}

func TestPulseHistory(t *testing.T) {
	t.Run("response data slice too short", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			isPublic := r.URL.Query().Get("isPublic")
			assert.Equal(t, "0", isPublic)

			respMock := []interface{}{
				[]interface{}{"id"},
			}
			payload, _ := json.Marshal(respMock)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pp, err := c.Pulse.PulseHistory(0)
		require.NotNil(t, err)
		require.Nil(t, pp)
	})

	t.Run("isPublic 0", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			isPublic := r.URL.Query().Get("isPublic")
			assert.Equal(t, "0", isPublic)

			respMock := []interface{}{
				[]interface{}{
					"id",
					float64(1591614631576),
					nil,
					"uid",
					nil,
					"title",
					"content",
					nil,
					nil,
					1,
					1,
					nil,
					[]interface{}{"tag1", "tag2"},
					[]interface{}{"attach1", "attach2"},
					nil,
					5,
					nil,
					nil,
					nil,
				},
			}
			payload, _ := json.Marshal(respMock)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pm, err := c.Pulse.PulseHistory(0)
		require.Nil(t, err)

		expected := &pulse.Pulse{
			ID:          "id",
			MTS:         1591614631576,
			UserID:      "uid",
			Title:       "title",
			Content:     "content",
			IsPin:       1,
			IsPublic:    1,
			Tags:        []string{"tag1", "tag2"},
			Attachments: []string{"attach1", "attach2"},
			Likes:       5,
		}

		assert.Equal(t, expected, pm[0])
	})

	t.Run("isPublic 1", func(t *testing.T) {
		handler := func(w http.ResponseWriter, r *http.Request) {
			isPublic := r.URL.Query().Get("isPublic")
			assert.Equal(t, "1", isPublic)

			respMock := []interface{}{
				[]interface{}{
					"id",
					float64(1591614631576),
					nil,
					"uid",
					nil,
					"title",
					"content",
					nil,
					nil,
					1,
					1,
					nil,
					[]interface{}{"tag1", "tag2"},
					[]interface{}{"attach1", "attach2"},
					nil,
					5,
					nil,
					nil,
					[]interface{}{
						[]interface{}{
							"abc123",
							float64(1591614631576),
							nil,
							"nickname",
							nil,
							"picture",
							"text",
							nil,
							nil,
							"twitter",
							nil,
							30,
							5,
							nil,
						},
					},
				},
			}
			payload, _ := json.Marshal(respMock)
			w.Write(payload)
		}

		server := httptest.NewServer(http.HandlerFunc(handler))
		defer server.Close()

		c := NewClientWithURL(server.URL)
		pm, err := c.Pulse.PulseHistory(1)
		require.Nil(t, err)

		expected := &pulse.Pulse{
			ID:          "id",
			MTS:         1591614631576,
			UserID:      "uid",
			Title:       "title",
			Content:     "content",
			IsPin:       1,
			IsPublic:    1,
			Tags:        []string{"tag1", "tag2"},
			Attachments: []string{"attach1", "attach2"},
			Likes:       5,
			PulseProfile: &pulseprofile.PulseProfile{
				ID:            "abc123",
				MTS:           1591614631576,
				Nickname:      "nickname",
				Picture:       "picture",
				Text:          "text",
				TwitterHandle: "twitter",
			},
		}

		assert.Equal(t, expected, pm[0])
	})
}
<|endoftext|>


<commit_before>/*
copyright 2019 google llc
licensed under the apache license, version 2.0 (the "license");
you may not use this file except in compliance with the license.
you may obtain a copy of the license at
    http://www.apache.org/licenses/license-2.0
unless required by applicable law or agreed to in writing, software
distributed under the license is distributed on an "as is" basis,
without warranties or conditions of any kind, either express or implied.
see the license for the specific language governing permissions and
limitations under the license.
*/

package cmd

import (
	"log"
	api "shifter/api"

	"github.com/spf13/cobra"
)

var (
	serverPort    string
	serverAddress string
)

var serverCmd = &cobra.Command{
	Use:   "server",
	Short: "Convert Openshift Resources to Kubernetes native formats via Shifter API",
	Long: `

	 _____ __    _ ______
	/ ___// /_  (_) __/ /____  _____       ___   ____ ____ ______  ___    ___   ____
	\__ \/ __ \/ / /_/ __/ _ \/ ___/      / _ \ / __// __//_  __/ / _ |  / _ \ /  _/
   ___/ / / / / / __/ /_/  __/ /         / , _// _/ _\ \   / /   / __ | / ___/_/ /
  /____/_/ /_/_/_/  \__/\___/_/         /_/|_|/___//___/  /_/   /_/ |_|/_/   /___/

Convert OpenShift resources to kubernetes native formats

Usage: shifter server

`,
	Run: func(cmd *cobra.Command, args []string) {
		log.Println(`
   _____ __    _ ______
  / ___// /_  (_) __/ /____  _____       ___   ____ ____ ______  ___    ___   ____
  \__ \/ __ \/ / /_/ __/ _ \/ ___/      / _ \ / __// __//_  __/ / _ |  / _ \ /  _/
 ___/ / / / / / __/ /_/  __/ /         / , _// _/ _\ \   / /   / __ | / ___/_/ /
/____/_/ /_/_/_/  \__/\___/_/         /_/|_|/___//___/  /_/   /_/ |_|/_/   /___/

-------------------------------------------------------------------------------------
			`)

		//flags := ProcFlags(pFlags)
		//err :=
		server, err := api.InitServer(serverAddress, serverPort, sourcePath, outputPath)
		if err != nil {
			log.Fatal("Cannot Create HTTP Server:", err)
		}
		server.Start()
		if err != nil {
			log.Fatal("Cannot Start HTTP Server:", err)
		}
	},
}

func init() {
	rootCmd.AddCommand(serverCmd)
	serverCmd.Flags().StringVarP(&serverPort, "port", "p", "8080", "Server Port: Default 8080")
	serverCmd.Flags().StringVarP(&serverAddress, "host-address", "a", "0.0.0.0", "Host Address: Default 0.0.0.0")
	serverCmd.Flags().StringVarP(&sourcePath, "source-path", "f", "", "Relative Local Path (./data/source) or Google Cloud Storage Bucket Path (gs://XXXXXXX/source/) for Source Files to be Written")
	serverCmd.Flags().StringVarP(&outputPath, "output-path", "o", "", "Relative Local Path (./data/output) or Google Cloud Storage Bucket Path (gs://XXXXXXX/output/) for Converted Files to be Written")
}
<commit_msg>Balancing UI<commit_after>/*
copyright 2019 google llc
licensed under the apache license, version 2.0 (the "license");
you may not use this file except in compliance with the license.
you may obtain a copy of the license at
    http://www.apache.org/licenses/license-2.0
unless required by applicable law or agreed to in writing, software
distributed under the license is distributed on an "as is" basis,
without warranties or conditions of any kind, either express or implied.
see the license for the specific language governing permissions and
limitations under the license.
*/

package cmd

import (
	"log"
	api "shifter/api"

	"github.com/spf13/cobra"
)

var (
	serverPort    string
	serverAddress string
)

var serverCmd = &cobra.Command{
	Use:   "server",
	Short: "Convert Openshift Resources to Kubernetes native formats via Shifter API",
	Long: `

	 _____ __    _ ______
	/ ___// /_  (_) __/ /____  _____       ___   ____ ____ ______  ___    ___   ____
	\__ \/ __ \/ / /_/ __/ _ \/ ___/      / _ \ / __// __//_  __/ / _ |  / _ \ /  _/
   ___/ / / / / / __/ /_/  __/ /         / , _// _/ _\ \   / /   / __ | / ___/_/ /
  /____/_/ /_/_/_/  \__/\___/_/         /_/|_|/___//___/  /_/   /_/ |_|/_/   /___/

Convert OpenShift resources to kubernetes native formats

Usage: shifter server

`,
	Run: func(cmd *cobra.Command, args []string) {
		log.Println(`
   _____ __    _ ______
  / ___// /_  (_) __/ /____  _____       ___   ____ ____ ______  ___    ___   ____
  \__ \/ __ \/ / /_/ __/ _ \/ ___/      / _ \ / __// __//_  __/ / _ |  / _ \ /  _/
 ___/ / / / / / __/ /_/  __/ /         / , _// _/ _\ \   / /   / __ | / ___/_/ /
/____/_/ /_/_/_/  \__/\___/_/         /_/|_|/___//___/  /_/   /_/ |_|/_/   /___/

-------------------------------------------------------------------------------------
			`)

		//flags := ProcFlags(pFlags)
		//err :=
		server, err := api.InitServer(serverAddress, serverPort, sourcePath, outputPath)
		if err != nil {
			log.Fatal("Cannot Create HTTP Server:", err)
		}
		server.Start()
		if err != nil {
			log.Fatal("Cannot Start HTTP Server:", err)
		}
	},
}

func init() {
	rootCmd.AddCommand(serverCmd)
	serverCmd.Flags().StringVarP(&serverPort, "port", "p", "8080", "Server Port: Default 8080")
	serverCmd.Flags().StringVarP(&serverAddress, "host-address", "a", "0.0.0.0", "Host Address: Default 0.0.0.0")
	serverCmd.Flags().StringVarP(&sourcePath, "source-path", "f", "", "Relative Local Path (./data/source) or Google Cloud Storage Bucket Path (gs://XXXXXXX/source/) for Source Files to be Written")
	serverCmd.Flags().StringVarP(&outputPath, "output-path", "o", "", "Relative Local Path (./data/output) or Google Cloud Storage Bucket Path (gs://XXXXXXX/output/) for Converted Files to be Written")
	serverCmd.Flags().StringVarP(&path, "path", "o", "", "Relative Local Path (./data/output) or Google Cloud Storage Bucket Path (gs://XXXXXXX/output/) for Converted Files to be Written")
	serverCmd.Flags().StringVarP(&storageType, "patstorage-type", "o", "", "LCL for Local or GCS for Google Cloud Storage Bucket")
}
<|endoftext|>


<commit_before>package main

import (
	"archive/zip"
	"encoding/base64"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

// MaxMind - GeoBase compatible generator for geolite.maxmind.com
type MaxMind struct {
	archive    []*zip.File
	OutputDir  string
	ErrorsChan chan Error
	lang       string
	ipver      int
	tzNames    bool
	include    string
	exclude    string
	noBase64   bool
	noCountry  bool
}

func (maxmind *MaxMind) name() string {
	return "MaxMind"
}

func (maxmind *MaxMind) addError(err Error) {
	maxmind.ErrorsChan <- err
}

func (maxmind *MaxMind) download() ([]byte, error) {
	resp, err := http.Get("http://geolite.maxmind.com/download/geoip/database/GeoLite2-City-CSV.zip")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	answer, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return answer, nil
}

func (maxmind *MaxMind) unpack(response []byte) error {
	file, err := Unpack(response)
	if err == nil {
		maxmind.archive = file
	}
	return err
}

func (maxmind *MaxMind) lineToItem(record []string, currentTime time.Time) (*string, *geoItem, string, error) {
	if len(record) < 13 {
		return nil, nil, "FAIL", errors.New("too short line")
	}
	countryCode := record[4]
	if len(countryCode) < 1 || len(record[5]) < 1 {
		return nil, nil, "", errors.New("too short country")
	}
	if len(maxmind.include) > 1 && !strings.Contains(maxmind.include, countryCode) {
		return nil, nil, "", errors.New("country skipped")
	}
	if strings.Contains(maxmind.exclude, countryCode) {
		return nil, nil, "", errors.New("country excluded")
	}
	tz := record[12]
	if !maxmind.tzNames {
		tz = convertTZToOffset(currentTime, record[12])
	}
	if len(record[10]) < 1 {
		return nil, nil, "", errors.New("too short city name")
	}
	return &record[0], &geoItem{
		ID:          record[0],
		City:        record[10],
		TZ:          tz,
		CountryCode: record[4],
		Country:     record[5],
	}, "", nil
}

func (maxmind *MaxMind) citiesDB() (map[string]geoItem, error) {
	locations := make(map[string]geoItem)
	currentTime := time.Now()
	filename := "GeoLite2-City-Locations-" + maxmind.lang + ".csv"
	for record := range readCSVDatabase(maxmind.archive, filename, "MaxMind", ',', false) {
		key, location, severity, err := maxmind.lineToItem(record, currentTime)
		if err != nil {
			if len(severity) > 0 {
				printMessage("MaxMind", fmt.Sprintf(filename+" %v", err), severity)
			}
			continue
		}
		locations[*key] = *location
	}
	if len(locations) < 1 {
		return nil, errors.New("Locations db is empty")
	}
	return locations, nil
}

func (maxmind *MaxMind) parseNetwork(locations map[string]geoItem) <-chan geoItem {
	database := make(chan geoItem)
	go func() {
		var ipRange string
		var geoID string
		filename := "GeoLite2-City-Blocks-IPv" + strconv.Itoa(maxmind.ipver) + ".csv"
		for record := range readCSVDatabase(maxmind.archive, filename, "MaxMind", ',', false) {
			if len(record) < 2 {
				printMessage("MaxMind", fmt.Sprintf(filename+" too short line: %s", record), "FAIL")
				continue
			}
			ipRange = getIPRange(maxmind.ipver, record[0])
			if ipRange == "" {
				continue
			}
			geoID = record[1]
			if location, ok := locations[geoID]; ok {
				location.Network = ipRange
				database <- location
			}
		}
		close(database)
	}()
	return database
}

func (maxmind *MaxMind) writeMap(locations map[string]geoItem) error {
	city, err := openMapFile(maxmind.OutputDir, "mm_city.txt")
	if err != nil {
		return err
	}
	tz, err := openMapFile(maxmind.OutputDir, "mm_tz.txt")
	if err != nil {
		return err
	}
	var country *os.File
	var countryCode *os.File
	if !maxmind.noCountry {
		country, err = openMapFile(maxmind.OutputDir, "mm_country.txt")
		if err != nil {
			return err
		}
		countryCode, err = openMapFile(maxmind.OutputDir, "mm_country_code.txt")
		if err != nil {
			return err
		}
		defer country.Close()
		defer countryCode.Close()
	}
	defer city.Close()
	defer tz.Close()

	for location := range maxmind.parseNetwork(locations) {
		var cityName string
		var countryName string
		if maxmind.noBase64 {
			cityName = "\"" + strings.ReplaceAll(location.City, "\"", "\\\"") + "\""
			countryName = "\"" + strings.ReplaceAll(location.Country, "\"", "\\\"") + "\""
		} else {
			cityName = base64.StdEncoding.EncodeToString([]byte(location.City))
			countryName = base64.StdEncoding.EncodeToString([]byte(location.Country))
		}

		fmt.Fprintf(city, "%s %s;\n", location.Network, cityName)
		fmt.Fprintf(tz, "%s %s;\n", location.Network, location.TZ)
		if !maxmind.noCountry {
			fmt.Fprintf(country, "%s %s;\n", location.Network, countryName)
			fmt.Fprintf(countryCode, "%s %s;\n", location.Network, location.CountryCode)
		}
	}
	return nil
}
<commit_msg>Use old strings.Replace(..,-1) for compability<commit_after>package main

import (
	"archive/zip"
	"encoding/base64"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

// MaxMind - GeoBase compatible generator for geolite.maxmind.com
type MaxMind struct {
	archive    []*zip.File
	OutputDir  string
	ErrorsChan chan Error
	lang       string
	ipver      int
	tzNames    bool
	include    string
	exclude    string
	noBase64   bool
	noCountry  bool
}

func (maxmind *MaxMind) name() string {
	return "MaxMind"
}

func (maxmind *MaxMind) addError(err Error) {
	maxmind.ErrorsChan <- err
}

func (maxmind *MaxMind) download() ([]byte, error) {
	resp, err := http.Get("http://geolite.maxmind.com/download/geoip/database/GeoLite2-City-CSV.zip")
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	answer, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return answer, nil
}

func (maxmind *MaxMind) unpack(response []byte) error {
	file, err := Unpack(response)
	if err == nil {
		maxmind.archive = file
	}
	return err
}

func (maxmind *MaxMind) lineToItem(record []string, currentTime time.Time) (*string, *geoItem, string, error) {
	if len(record) < 13 {
		return nil, nil, "FAIL", errors.New("too short line")
	}
	countryCode := record[4]
	if len(countryCode) < 1 || len(record[5]) < 1 {
		return nil, nil, "", errors.New("too short country")
	}
	if len(maxmind.include) > 1 && !strings.Contains(maxmind.include, countryCode) {
		return nil, nil, "", errors.New("country skipped")
	}
	if strings.Contains(maxmind.exclude, countryCode) {
		return nil, nil, "", errors.New("country excluded")
	}
	tz := record[12]
	if !maxmind.tzNames {
		tz = convertTZToOffset(currentTime, record[12])
	}
	if len(record[10]) < 1 {
		return nil, nil, "", errors.New("too short city name")
	}
	return &record[0], &geoItem{
		ID:          record[0],
		City:        record[10],
		TZ:          tz,
		CountryCode: record[4],
		Country:     record[5],
	}, "", nil
}

func (maxmind *MaxMind) citiesDB() (map[string]geoItem, error) {
	locations := make(map[string]geoItem)
	currentTime := time.Now()
	filename := "GeoLite2-City-Locations-" + maxmind.lang + ".csv"
	for record := range readCSVDatabase(maxmind.archive, filename, "MaxMind", ',', false) {
		key, location, severity, err := maxmind.lineToItem(record, currentTime)
		if err != nil {
			if len(severity) > 0 {
				printMessage("MaxMind", fmt.Sprintf(filename+" %v", err), severity)
			}
			continue
		}
		locations[*key] = *location
	}
	if len(locations) < 1 {
		return nil, errors.New("Locations db is empty")
	}
	return locations, nil
}

func (maxmind *MaxMind) parseNetwork(locations map[string]geoItem) <-chan geoItem {
	database := make(chan geoItem)
	go func() {
		var ipRange string
		var geoID string
		filename := "GeoLite2-City-Blocks-IPv" + strconv.Itoa(maxmind.ipver) + ".csv"
		for record := range readCSVDatabase(maxmind.archive, filename, "MaxMind", ',', false) {
			if len(record) < 2 {
				printMessage("MaxMind", fmt.Sprintf(filename+" too short line: %s", record), "FAIL")
				continue
			}
			ipRange = getIPRange(maxmind.ipver, record[0])
			if ipRange == "" {
				continue
			}
			geoID = record[1]
			if location, ok := locations[geoID]; ok {
				location.Network = ipRange
				database <- location
			}
		}
		close(database)
	}()
	return database
}

func (maxmind *MaxMind) writeMap(locations map[string]geoItem) error {
	city, err := openMapFile(maxmind.OutputDir, "mm_city.txt")
	if err != nil {
		return err
	}
	tz, err := openMapFile(maxmind.OutputDir, "mm_tz.txt")
	if err != nil {
		return err
	}
	var country *os.File
	var countryCode *os.File
	if !maxmind.noCountry {
		country, err = openMapFile(maxmind.OutputDir, "mm_country.txt")
		if err != nil {
			return err
		}
		countryCode, err = openMapFile(maxmind.OutputDir, "mm_country_code.txt")
		if err != nil {
			return err
		}
		defer country.Close()
		defer countryCode.Close()
	}
	defer city.Close()
	defer tz.Close()

	for location := range maxmind.parseNetwork(locations) {
		var cityName string
		var countryName string
		if maxmind.noBase64 {
			cityName = "\"" + strings.Replace(location.City, "\"", "\\\"", -1) + "\""
			countryName = "\"" + strings.Replace(location.Country, "\"", "\\\"", -1) + "\""
		} else {
			cityName = base64.StdEncoding.EncodeToString([]byte(location.City))
			countryName = base64.StdEncoding.EncodeToString([]byte(location.Country))
		}

		fmt.Fprintf(city, "%s %s;\n", location.Network, cityName)
		fmt.Fprintf(tz, "%s %s;\n", location.Network, location.TZ)
		if !maxmind.noCountry {
			fmt.Fprintf(country, "%s %s;\n", location.Network, countryName)
			fmt.Fprintf(countryCode, "%s %s;\n", location.Network, location.CountryCode)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package stm

import (
	"fmt"
	"log"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
)

// NewLocation returns created the Location's pointer
func NewLocation(opts *Options) *Location {
	loc := &Location{
		opts: opts,
	}
	return loc
}

// Location provides sitemap's path and filename on file systems
// and it provides proxy for Adapter interface also.
type Location struct {
	opts     *Options
	nmr      *Namer
	filename string
}

// Directory returns path to combine publicPath and sitemapsPath on file systems.
// It also indicates where sitemap files are.
func (loc *Location) Directory() string {
	return filepath.Join(
		loc.opts.publicPath,
		loc.opts.sitemapsPath,
	)
}

// Path returns path to combine publicPath, sitemapsPath and Filename on file systems.
// It also indicates where sitemap name is.
func (loc *Location) Path() string {
	return filepath.Join(
		loc.opts.publicPath,
		loc.opts.sitemapsPath,
		loc.Filename(),
	)
}

// PathInPublic returns path to combine sitemapsPath and Filename on website.
// It also indicates where url file path is.
func (loc *Location) PathInPublic() string {
	return filepath.Join(
		loc.opts.sitemapsPath,
		loc.Filename(),
	)
}

// URL returns path to combine SitemapsHost, sitemapsPath and
// Filename on website with it uses ResolveReference.
func (loc *Location) URL() string {
	base, _ := url.Parse(loc.opts.SitemapsHost())

	var u *url.URL
	for _, ref := range []string{
		loc.opts.sitemapsPath, loc.Filename(),
	} {
		u, _ = url.Parse(ref)
		base = base.ResolveReference(u)
	}

	return base.String()
}

// Filesize returns file size this struct has.
func (loc *Location) Filesize() int64 {
	f, _ := os.Open(loc.Path())
	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		return 0
	}

	return fi.Size()
}

// reGzip determines gzip file.
var reGzip = regexp.MustCompile(`\.gz$`)

// Namer returns the Namer's pointer that Options struct has.
func (loc *Location) Namer() *Namer {
	return loc.opts.Namer()
}

// Filename returns sitemap filename.
func (loc *Location) Filename() string {
	nmr := loc.Namer()
	if loc.filename == "" && nmr == nil {
		log.Fatal("[F] No filename or namer set")
	}

	if loc.filename == "" {
		loc.filename = nmr.String()

		if !loc.opts.compress {
			newName := reGzip.ReplaceAllString(loc.filename, "")
			loc.filename = newName
		}
	}
	return loc.filename
}

// ReserveName returns that sets filename if this struct didn't keep filename and
// it returns reserved filename if this struct keeps filename also.
func (loc *Location) ReserveName() string {
	nmr := loc.Namer()
	if nmr != nil {
		loc.Filename()
		nmr.Next()
	}

	return loc.filename
}

// IsReservedName confirms that keeps filename on Location.filename.
func (loc *Location) IsReservedName() bool {
	if loc.filename == "" {
		return false
	}
	return true
}

// IsVerbose returns boolean about verbosed summary.
func (loc *Location) IsVerbose() bool {
	return loc.opts.verbose
}

// Write writes sitemap and index files that used from Adapter interface.
func (loc *Location) Write(data []byte, linkCount int) {

	loc.opts.adp.Write(loc, data)
	if !loc.IsVerbose() {
		return
	}

	output := loc.Summary(linkCount)
	if output != "" {
		println(output)
	}
}

// Summary outputs to generated file summary for console.
func (loc *Location) Summary(linkCount int) string {
	nmr := loc.Namer()
	if nmr.IsStart() {
		return ""
	}

	out := fmt.Sprintf("%s '%d' links",
		loc.PathInPublic(), linkCount)

	size := loc.Filesize()
	if size <= 1 {
		return out
	}

	return fmt.Sprintf("%s / %d bytes", out, size)
}
<commit_msg>tiny fix<commit_after>package stm

import (
	"fmt"
	"log"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
)

// NewLocation returns created the Location's pointer
func NewLocation(opts *Options) *Location {
	loc := &Location{
		opts: opts,
	}
	return loc
}

// Location provides sitemap's path and filename on file systems
// and it provides proxy for Adapter interface also.
type Location struct {
	opts     *Options
	nmr      *Namer
	filename string
}

// Directory returns path to combine publicPath and sitemapsPath on file systems.
// It also indicates where sitemap files are.
func (loc *Location) Directory() string {
	return filepath.Join(
		loc.opts.publicPath,
		loc.opts.sitemapsPath,
	)
}

// Path returns path to combine publicPath, sitemapsPath and Filename on file systems.
// It also indicates where sitemap name is.
func (loc *Location) Path() string {
	return filepath.Join(
		loc.opts.publicPath,
		loc.opts.sitemapsPath,
		loc.Filename(),
	)
}

// PathInPublic returns path to combine sitemapsPath and Filename on website.
// It also indicates where url file path is.
func (loc *Location) PathInPublic() string {
	return filepath.Join(
		loc.opts.sitemapsPath,
		loc.Filename(),
	)
}

// URL returns path to combine SitemapsHost, sitemapsPath and
// Filename on website with it uses ResolveReference.
func (loc *Location) URL() string {
	base, _ := url.Parse(loc.opts.SitemapsHost())

	var u *url.URL
	for _, ref := range []string{
		loc.opts.sitemapsPath, loc.Filename(),
	} {
		u, _ = url.Parse(ref)
		base = base.ResolveReference(u)
	}

	return base.String()
}

// Filesize returns file size this struct has.
func (loc *Location) Filesize() int64 {
	f, _ := os.Open(loc.Path())
	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		return 0
	}

	return fi.Size()
}

// reGzip determines gzip file.
var reGzip = regexp.MustCompile(`\.gz$`)

// Namer returns the Namer's pointer that Options struct has.
func (loc *Location) Namer() *Namer {
	return loc.opts.Namer()
}

// Filename returns sitemap filename.
func (loc *Location) Filename() string {
	nmr := loc.Namer()
	if loc.filename == "" && nmr == nil {
		log.Fatal("[F] No filename or namer set")
	}

	if loc.filename == "" {
		loc.filename = nmr.String()

		if !loc.opts.compress {
			newName := reGzip.ReplaceAllString(loc.filename, "")
			loc.filename = newName
		}
	}
	return loc.filename
}

// ReserveName returns that sets filename if this struct didn't keep filename and
// it returns reserved filename if this struct keeps filename also.
func (loc *Location) ReserveName() string {
	nmr := loc.Namer()
	if nmr != nil {
		loc.Filename()
		nmr.Next()
	}

	return loc.filename
}

// IsReservedName confirms that keeps filename on Location.filename.
func (loc *Location) IsReservedName() bool {
	if loc.filename == "" {
		return false
	}
	return true
}

// IsVerbose returns boolean about verbosed summary.
func (loc *Location) IsVerbose() bool {
	return loc.opts.verbose
}

// Write writes sitemap and index files that used from Adapter interface.
func (loc *Location) Write(data []byte, linkCount int) {

	loc.opts.adp.Write(loc, data)
	if !loc.IsVerbose() {
		return
	}

	output := loc.Summary(linkCount)
	if output != "" {
		println(output)
	}
}

// Summary outputs to generated file summary for console.
func (loc *Location) Summary(linkCount int) string {
	nmr := loc.Namer()
	if nmr.IsStart() {
		return ""
	}

	out := fmt.Sprintf("%s '%d' links",
		loc.PathInPublic(), linkCount)

	size := loc.Filesize()
	if size <= 0 {
		return out
	}

	return fmt.Sprintf("%s / %d bytes", out, size)
}
<|endoftext|>


<commit_before>package storeapi

import (
	"encoding/json"
	"time"
)

type RequestData struct {
	ClientIP string
	ServerIP string
	EdnsNet  string
	TestIP   string
}

type LogData struct {
	ClientIP  string     `db:"client_ip"`
	ServerIP  string     `db:"server_ip"`
	EdnsNet   string     `db:"edns_net"`
	ClientCC  string     `db:"client_cc"`
	ClientRC  string     `db:"client_rc"`
	ServerCC  string     `db:"server_cc"`
	ServerRC  string     `db:"server_rc"`
	EdnsCC    string     `db:"edns_cc"`
	EdnsRC    string     `db:"edns_rc""`
	ClientASN int        `db:"client_asn"`
	ServerASN int        `db:"server_asn"`
	EdnsASN   int        `db:"edns_asn"`
	HasEdns   bool       `db:"has_edns"`
	TestIP    string     `db:"test_ip" json:"-"`
	FirstSeen *time.Time `db:"first_seen" json:"-"`
	LastSeen  *time.Time `db:"last_seen" json:"-"`
}

func (data *RequestData) JSON() ([]byte, error) {
	return json.Marshal(data)
}
<commit_msg>Minor syntax error<commit_after>package storeapi

import (
	"encoding/json"
	"time"
)

type RequestData struct {
	ClientIP string
	ServerIP string
	EdnsNet  string
	TestIP   string
}

type LogData struct {
	ClientIP  string     `db:"client_ip"`
	ServerIP  string     `db:"server_ip"`
	EdnsNet   string     `db:"edns_net"`
	ClientCC  string     `db:"client_cc"`
	ClientRC  string     `db:"client_rc"`
	ServerCC  string     `db:"server_cc"`
	ServerRC  string     `db:"server_rc"`
	EdnsCC    string     `db:"edns_cc"`
	EdnsRC    string     `db:"edns_rc"`
	ClientASN int        `db:"client_asn"`
	ServerASN int        `db:"server_asn"`
	EdnsASN   int        `db:"edns_asn"`
	HasEdns   bool       `db:"has_edns"`
	TestIP    string     `db:"test_ip" json:"-"`
	FirstSeen *time.Time `db:"first_seen" json:"-"`
	LastSeen  *time.Time `db:"last_seen" json:"-"`
}

func (data *RequestData) JSON() ([]byte, error) {
	return json.Marshal(data)
}
<|endoftext|>


<commit_before>package test

import (
	"context"
	"fmt"
	"net"
	"reflect"
	"regexp"

	"time"

	"sync"

	"github.com/miekg/dns"
)

var (
	muDefaultResolver  sync.RWMutex
	DomainsToAddresses = map[string][]string{
		"host1.local.": {"127.0.0.1"},
		"host2.local.": {"127.0.0.1"},
		"host3.local.": {"127.0.0.1"},
	}
)

type dnsMockHandler struct {
	domainsToAddresses map[string][]string
	domainsToErrors    map[string]int

	muDomainsToAddresses sync.RWMutex
}

func (d *dnsMockHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
	msg := dns.Msg{}
	msg.SetReply(r)
	switch r.Question[0].Qtype {
	case dns.TypeA:
		msg.Authoritative = true
		domain := msg.Question[0].Name

		d.muDomainsToAddresses.RLock()
		defer d.muDomainsToAddresses.RUnlock()

		if rcode, ok := d.domainsToErrors[domain]; ok {
			m := new(dns.Msg)
			m.SetRcode(r, rcode)
			w.WriteMsg(m)
			return
		}

		addresses, ok := d.domainsToAddresses[domain]
		if !ok {
			// ^ 				start of line
			// localhost\.		match literally
			// ()* 				match between 0 and unlimited times
			// [[:alnum:]]+\.	match single character in [a-zA-Z0-9] minimum one time and ending in . literally
			reg := regexp.MustCompile(`^localhost\.([[:alnum:]]+\.)*`)
			if matched := reg.MatchString(domain); !matched {
				panic(fmt.Sprintf("domain not mocked: %s", domain))
			}

			addresses = []string{"127.0.0.1"}
		}

		for _, addr := range addresses {
			msg.Answer = append(msg.Answer, &dns.A{
				Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60},
				A:   net.ParseIP(addr),
			})
		}
	}
	w.WriteMsg(&msg)
}

type DnsMockHandle struct {
	id              string
	mockServer      *dns.Server
	ShutdownDnsMock func() error
}

func (h *DnsMockHandle) PushDomains(domainsMap map[string][]string, domainsErrorMap map[string]int) func() {
	handler := h.mockServer.Handler.(*dnsMockHandler)
	handler.muDomainsToAddresses.Lock()
	defer handler.muDomainsToAddresses.Unlock()

	dta := handler.domainsToAddresses
	dte := handler.domainsToErrors

	prevDta := map[string][]string{}
	prevDte := map[string]int{}

	for key, value := range dta {
		prevDta[key] = value
	}

	for key, value := range dte {
		prevDte[key] = value
	}

	pullDomainsFunc := func() {
		handler := h.mockServer.Handler.(*dnsMockHandler)
		handler.muDomainsToAddresses.Lock()
		defer handler.muDomainsToAddresses.Unlock()

		handler.domainsToAddresses = prevDta
		handler.domainsToErrors = prevDte
	}

	for key, ips := range domainsMap {
		addr, ok := dta[key]
		if !ok {
			dta[key] = ips
		} else {
			dta[key] = append(addr, ips...)
		}
	}

	for key, rCode := range domainsErrorMap {
		dte[key] = rCode
	}

	return pullDomainsFunc
}

// InitDNSMock initializes dns server on udp:0 address and replaces net.DefaultResolver in order
// to route all dns queries within tests to this server.
// InitDNSMock returns handle, which can be used to add/remove dns query mock responses or initialization error.
func InitDNSMock(domainsMap map[string][]string, domainsErrorMap map[string]int) (*DnsMockHandle, error) {
	addr, _ := net.ResolveUDPAddr("udp", ":0")
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		return &DnsMockHandle{}, err
	}

	startResultChannel := make(chan error)
	started := func() {
		startResultChannel <- nil
	}

	mockServer := &dns.Server{PacketConn: conn, NotifyStartedFunc: started}
	handle := &DnsMockHandle{id: time.Now().String(), mockServer: mockServer}

	dnsMux := &dnsMockHandler{muDomainsToAddresses: sync.RWMutex{}}

	if domainsMap != nil {
		dnsMux.domainsToAddresses = domainsMap
	} else {
		dnsMux.domainsToAddresses = DomainsToAddresses
	}

	if domainsErrorMap != nil {
		dnsMux.domainsToErrors = domainsErrorMap
	}

	mockServer.Handler = dnsMux

	go func() {
		startResultChannel <- mockServer.ActivateAndServe()
	}()

	err = <-startResultChannel
	if err != nil {
		close(startResultChannel)
		return handle, err
	}

	muDefaultResolver.RLock()
	defaultResolver := net.DefaultResolver
	muDefaultResolver.RUnlock()
	mockResolver := &net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			d := net.Dialer{}

			//Use write lock to prevent unsafe d.DialContext update of net.DefaultResolver
			muDefaultResolver.Lock()
			defer muDefaultResolver.Unlock()
			return d.DialContext(ctx, network, mockServer.PacketConn.LocalAddr().String())
		},
	}

	muDefaultResolver.Lock()
	net.DefaultResolver = mockResolver
	muDefaultResolver.Unlock()

	handle.ShutdownDnsMock = func() error {
		muDefaultResolver.Lock()
		net.DefaultResolver = defaultResolver
		muDefaultResolver.Unlock()

		return mockServer.Shutdown()
	}

	return handle, nil
}

func IsDnsRecordsAddrsEqualsTo(itemAddrs, addrs []string) bool {
	return reflect.DeepEqual(itemAddrs, addrs)
}
<commit_msg>DNS mocks should be disabled for redis hosts<commit_after>package test

import (
	"context"
	"fmt"
	"net"
	"reflect"
	"regexp"
	"strings"

	"time"

	"sync"

	"github.com/miekg/dns"
)

var (
	muDefaultResolver  sync.RWMutex
	DomainsToAddresses = map[string][]string{
		"host1.local.": {"127.0.0.1"},
		"host2.local.": {"127.0.0.1"},
		"host3.local.": {"127.0.0.1"},
	}
	DomainsToIgnore = []string{
		"redis.",
		"tyk-redis.",
	}
)

type dnsMockHandler struct {
	domainsToAddresses map[string][]string
	domainsToErrors    map[string]int

	muDomainsToAddresses sync.RWMutex
}

func (d *dnsMockHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {
	msg := dns.Msg{}
	msg.SetReply(r)
	switch r.Question[0].Qtype {
	case dns.TypeA:
		msg.Authoritative = true
		domain := msg.Question[0].Name

		d.muDomainsToAddresses.RLock()
		defer d.muDomainsToAddresses.RUnlock()

		if rcode, ok := d.domainsToErrors[domain]; ok {
			m := new(dns.Msg)
			m.SetRcode(r, rcode)
			w.WriteMsg(m)
			return
		}

		for _, ignore := range DomainsToIgnore {
			if strings.HasPrefix(domain, ignore) {
				resolver := &net.Resolver{}
				addrs, err := resolver.LookupAddr(context.Background(), domain)
				if err != nil {
					m := new(dns.Msg)
					m.SetRcode(r, dns.RcodeServerFailure)
					w.WriteMsg(m)
					return
				}
				msg.Answer = append(msg.Answer, &dns.A{
					Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60},
					A:   net.ParseIP(addrs[0]),
				})
				w.WriteMsg(&msg)
				return
			}
		}

		addresses, ok := d.domainsToAddresses[domain]
		if !ok {
			// ^ 				start of line
			// localhost\.		match literally
			// ()* 				match between 0 and unlimited times
			// [[:alnum:]]+\.	match single character in [a-zA-Z0-9] minimum one time and ending in . literally
			reg := regexp.MustCompile(`^localhost\.([[:alnum:]]+\.)*`)
			if matched := reg.MatchString(domain); !matched {
				panic(fmt.Sprintf("domain not mocked: %s", domain))
			}

			addresses = []string{"127.0.0.1"}
		}

		for _, addr := range addresses {
			msg.Answer = append(msg.Answer, &dns.A{
				Hdr: dns.RR_Header{Name: domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60},
				A:   net.ParseIP(addr),
			})
		}
	}
	w.WriteMsg(&msg)
}

type DnsMockHandle struct {
	id              string
	mockServer      *dns.Server
	ShutdownDnsMock func() error
}

func (h *DnsMockHandle) PushDomains(domainsMap map[string][]string, domainsErrorMap map[string]int) func() {
	handler := h.mockServer.Handler.(*dnsMockHandler)
	handler.muDomainsToAddresses.Lock()
	defer handler.muDomainsToAddresses.Unlock()

	dta := handler.domainsToAddresses
	dte := handler.domainsToErrors

	prevDta := map[string][]string{}
	prevDte := map[string]int{}

	for key, value := range dta {
		prevDta[key] = value
	}

	for key, value := range dte {
		prevDte[key] = value
	}

	pullDomainsFunc := func() {
		handler := h.mockServer.Handler.(*dnsMockHandler)
		handler.muDomainsToAddresses.Lock()
		defer handler.muDomainsToAddresses.Unlock()

		handler.domainsToAddresses = prevDta
		handler.domainsToErrors = prevDte
	}

	for key, ips := range domainsMap {
		addr, ok := dta[key]
		if !ok {
			dta[key] = ips
		} else {
			dta[key] = append(addr, ips...)
		}
	}

	for key, rCode := range domainsErrorMap {
		dte[key] = rCode
	}

	return pullDomainsFunc
}

// InitDNSMock initializes dns server on udp:0 address and replaces net.DefaultResolver in order
// to route all dns queries within tests to this server.
// InitDNSMock returns handle, which can be used to add/remove dns query mock responses or initialization error.
func InitDNSMock(domainsMap map[string][]string, domainsErrorMap map[string]int) (*DnsMockHandle, error) {
	addr, _ := net.ResolveUDPAddr("udp", ":0")
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		return &DnsMockHandle{}, err
	}

	startResultChannel := make(chan error)
	started := func() {
		startResultChannel <- nil
	}

	mockServer := &dns.Server{PacketConn: conn, NotifyStartedFunc: started}
	handle := &DnsMockHandle{id: time.Now().String(), mockServer: mockServer}

	dnsMux := &dnsMockHandler{muDomainsToAddresses: sync.RWMutex{}}

	if domainsMap != nil {
		dnsMux.domainsToAddresses = domainsMap
	} else {
		dnsMux.domainsToAddresses = DomainsToAddresses
	}

	if domainsErrorMap != nil {
		dnsMux.domainsToErrors = domainsErrorMap
	}

	mockServer.Handler = dnsMux

	go func() {
		startResultChannel <- mockServer.ActivateAndServe()
	}()

	err = <-startResultChannel
	if err != nil {
		close(startResultChannel)
		return handle, err
	}

	muDefaultResolver.RLock()
	defaultResolver := net.DefaultResolver
	muDefaultResolver.RUnlock()
	mockResolver := &net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			d := net.Dialer{}

			//Use write lock to prevent unsafe d.DialContext update of net.DefaultResolver
			muDefaultResolver.Lock()
			defer muDefaultResolver.Unlock()
			return d.DialContext(ctx, network, mockServer.PacketConn.LocalAddr().String())
		},
	}

	muDefaultResolver.Lock()
	net.DefaultResolver = mockResolver
	muDefaultResolver.Unlock()

	handle.ShutdownDnsMock = func() error {
		muDefaultResolver.Lock()
		net.DefaultResolver = defaultResolver
		muDefaultResolver.Unlock()

		return mockServer.Shutdown()
	}

	return handle, nil
}

func IsDnsRecordsAddrsEqualsTo(itemAddrs, addrs []string) bool {
	return reflect.DeepEqual(itemAddrs, addrs)
}
<|endoftext|>


<commit_before>package gochimp3

import (
	"errors"
	"fmt"
)

const (
	members_path       = "/lists/%s/members"
	single_member_path = members_path + "/%s"

	member_activity_path = single_member_path + "/activity"
	member_goals_path    = single_member_path + "/goals"

	member_notes_path       = single_member_path + "/notes"
	single_member_note_path = member_notes_path + "/%s"
)

type ListOfMembers struct {
	baseList

	ListID  string   `json:"list_id"`
	Members []Member `json:"members"`
}

type MemberResponse struct {
	EmailAddress    string                 `json:"email_address"`
	EmailType       string                 `json:"email_type,omitempty"`
	Status          string                 `json:"status"`
	StatusIfNew     string                 `json:"status_if_new,omitempty"`
	MergeFields     map[string]interface{} `json:"merge_fields,omitempty"`
	Interests       map[string]bool        `json:"interests,omitempty"`
	Language        string                 `json:"language"`
	VIP             bool                   `json:"vip"`
	Location        *MemberLocation        `json:"location,omitempty"`
	IPOpt           string                 `json:"ip_opt,omitempty"`
	IPSignup        string                 `json:"ip_signup,omitempty"`
	Tags            []MemberTag            `json:"tags,omitempty"`
	TimestampSignup string                 `json:"timestamp_signup,omitempty"`
	TimestampOpt    string                 `json:"timestamp_opt,omitempty"`
}

type MemberRequest struct {
	EmailAddress         string                 `json:"email_address"`
	EmailType            string                 `json:"email_type,omitempty"`
	Status               string                 `json:"status"`
	StatusIfNew          string                 `json:"status_if_new,omitempty"`
	MergeFields          map[string]interface{} `json:"merge_fields,omitempty"`
	Interests            map[string]bool        `json:"interests,omitempty"`
	Language             string                 `json:"language"`
	VIP                  bool                   `json:"vip"`
	Location             *MemberLocation        `json:"location,omitempty"`
	MarketingPermissions *MarketingPermissions  `json:"marketing_permissions,omitempty"`
	IPOpt                string                 `json:"ip_opt,omitempty"`
	IPSignup             string                 `json:"ip_signup,omitempty"`
	Tags                 []string               `json:"tags,omitempty"`
	TimestampSignup      string                 `json:"timestamp_signup,omitempty"`
	TimestampOpt         string                 `json:"timestamp_opt,omitempty"`
}

type Member struct {
	MemberResponse

	ID            string          `json:"id"`
	ListID        string          `json:"list_id"`
	UniqueEmailID string          `json:"unique_email_id"`
	EmailType     string          `json:"email_type"`
	Stats         MemberStats     `json:"stats"`
	MemberRating  int             `json:"member_rating"`
	LastChanged   string          `json:"last_changed"`
	EmailClient   string          `json:"email_client"`
	LastNote      MemberNoteShort `json:"last_note"`

	api *API
}

func (mem Member) CanMakeRequest() error {
	if mem.ListID == "" {
		return errors.New("No ListID provided")
	}

	if mem.ID == "" {
		return errors.New("No ID provided")
	}

	return nil
}

type MemberStats struct {
	AvgOpenRate  float64 `json:"avg_open_rate"`
	AvgClickRate float64 `json:"avg_click_rate"`
}

type MemberLocation struct {
	Latitude    float64 `json:"latitude"`
	Longitude   float64 `json:"longitude"`
	GMTOffset   int     `json:"gmtoff"`
	DSTOffset   int     `json:"dstoff"`
	CountryCode string  `json:"country_code"`
	Timezone    string  `json:"timezone"`
}

type MarketingPermissions []MarketingPermission

type MarketingPermission struct {
	MarketingPermissionID string `json:"marketing_permission_id"`
	Text                  string `json:"text"`
	Enabled               bool   `json:"enabled"`
}

type MemberNoteShort struct {
	ID        int    `json:"note_id"`
	CreatedAt string `json:"created_at"`
	CreatedBy string `json:"created_by"`
	Note      string `json:"note"`
}

type MemberTag struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func (list ListResponse) GetMembers(params *InterestCategoriesQueryParams) (*ListOfMembers, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(members_path, list.ID)
	response := new(ListOfMembers)

	err := list.api.Request("GET", endpoint, params, nil, response)
	if err != nil {
		return nil, err
	}

	for _, m := range response.Members {
		m.api = list.api
	}

	return response, nil
}

func (list ListResponse) GetMember(id string, params *BasicQueryParams) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("GET", endpoint, params, nil, response)
}

func (list ListResponse) CreateMember(body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(members_path, list.ID)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("POST", endpoint, nil, body, response)
}

func (list ListResponse) UpdateMember(id string, body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("PATCH", endpoint, nil, body, response)
}

func (list ListResponse) AddOrUpdateMember(id string, body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("PUT", endpoint, nil, body, response)
}

func (list ListResponse) DeleteMember(id string) (bool, error) {
	if err := list.CanMakeRequest(); err != nil {
		return false, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	return list.api.RequestOk("DELETE", endpoint)
}

// ------------------------------------------------------------------------------------------------
// Activity
// ------------------------------------------------------------------------------------------------

type ListOfMemberActivity struct {
	baseList

	EmailID  string     `json:"email_id"`
	ListID   string     `json:"list_id"`
	Activity []Activity `json:"activity"`
}

type MemberActivity struct {
	Action         string `json:"action"`
	Timestamp      string `json:"timestamp"`
	URL            string `json:"url"`
	Type           string `json:"type"`
	CampaignID     string `json:"campaign_id"`
	Title          string `json:"title"`
	ParentCampaign string `json:"parent_campaign"`
}

func (mem Member) GetActivity(params *BasicQueryParams) (*ListOfMemberActivity, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_activity_path, mem.ListID, mem.ID)
	response := new(ListOfMemberActivity)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

// ------------------------------------------------------------------------------------------------
// Goals
// ------------------------------------------------------------------------------------------------

type ListOfMemberGoals struct {
	baseList

	EmailID string       `json:"email_id"`
	ListID  string       `json:"list_id"`
	Goals   []MemberGoal `json:"goals"`
}

type MemberGoal struct {
	ID            int    `json:"goal_id"`
	Event         string `json:"event"`
	LastVisitedAt string `json:"last_visited_at"`
	Data          string `json:"data"`
}

func (mem Member) GetGoals(params *BasicQueryParams) (*ListOfMemberGoals, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_goals_path, mem.ListID, mem.ID)
	response := new(ListOfMemberGoals)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

// ------------------------------------------------------------------------------------------------
// NOTES
// ------------------------------------------------------------------------------------------------

type ListOfMemberNotes struct {
	baseList

	EmailID string           `json:"email_id"`
	ListID  string           `json:"list_id"`
	Notes   []MemberNoteLong `json:"notes"`
}

type MemberNoteLong struct {
	ID        int    `json:"id"`
	CreatedAt string `json:"created_at"`
	CreatedBy string `json:"created_by"`
	UpdatedAt string `json:"updated_at"`
	Note      string `json:"note"`
	ListID    string `json:"list_id"`
	EmailID   string `json:"email_id"`

	withLinks
}

func (mem Member) GetNotes(params *ExtendedQueryParams) (*ListOfMemberNotes, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_notes_path, mem.ListID, mem.ID)
	response := new(ListOfMemberNotes)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

func (mem Member) CreateNote(msg string) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_notes_path, mem.ListID, mem.ID)
	response := new(MemberNoteLong)

	body := struct{ Note string }{
		Note: msg,
	}

	return response, mem.api.Request("POST", endpoint, nil, &body, response)
}

func (mem Member) UpdateNote(id, msg string) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	response := new(MemberNoteLong)

	body := struct{ Note string }{
		Note: msg,
	}

	return response, mem.api.Request("PATCH", endpoint, nil, &body, response)
}

func (mem Member) GetNote(id string, params *BasicQueryParams) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	response := new(MemberNoteLong)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

func (mem Member) DeleteNote(id string) (bool, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return false, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	return mem.api.RequestOk("DELETE", endpoint)
}
<commit_msg>Introduce Tags<commit_after>package gochimp3

import (
	"errors"
	"fmt"
)

const (
	members_path       = "/lists/%s/members"
	single_member_path = members_path + "/%s"

	member_activity_path = single_member_path + "/activity"
	member_goals_path    = single_member_path + "/goals"

	member_notes_path       = single_member_path + "/notes"
	single_member_note_path = member_notes_path + "/%s"

	member_tags_path       = single_member_path + "/tag"
	single_member_tag_path = member_notes_path + "/%s"
)

type ListOfMembers struct {
	baseList

	ListID  string   `json:"list_id"`
	Members []Member `json:"members"`
}

type MemberResponse struct {
	EmailAddress    string                 `json:"email_address"`
	EmailType       string                 `json:"email_type,omitempty"`
	Status          string                 `json:"status"`
	StatusIfNew     string                 `json:"status_if_new,omitempty"`
	MergeFields     map[string]interface{} `json:"merge_fields,omitempty"`
	Interests       map[string]bool        `json:"interests,omitempty"`
	Language        string                 `json:"language"`
	VIP             bool                   `json:"vip"`
	Location        *MemberLocation        `json:"location,omitempty"`
	IPOpt           string                 `json:"ip_opt,omitempty"`
	IPSignup        string                 `json:"ip_signup,omitempty"`
	Tags            []MemberTag            `json:"tags,omitempty"`
	TimestampSignup string                 `json:"timestamp_signup,omitempty"`
	TimestampOpt    string                 `json:"timestamp_opt,omitempty"`
}

type MemberRequest struct {
	EmailAddress         string                 `json:"email_address"`
	EmailType            string                 `json:"email_type,omitempty"`
	Status               string                 `json:"status"`
	StatusIfNew          string                 `json:"status_if_new,omitempty"`
	MergeFields          map[string]interface{} `json:"merge_fields,omitempty"`
	Interests            map[string]bool        `json:"interests,omitempty"`
	Language             string                 `json:"language"`
	VIP                  bool                   `json:"vip"`
	Location             *MemberLocation        `json:"location,omitempty"`
	MarketingPermissions *MarketingPermissions  `json:"marketing_permissions,omitempty"`
	IPOpt                string                 `json:"ip_opt,omitempty"`
	IPSignup             string                 `json:"ip_signup,omitempty"`
	Tags                 []string               `json:"tags,omitempty"`
	TimestampSignup      string                 `json:"timestamp_signup,omitempty"`
	TimestampOpt         string                 `json:"timestamp_opt,omitempty"`
}

type Member struct {
	MemberResponse

	ID            string          `json:"id"`
	ListID        string          `json:"list_id"`
	UniqueEmailID string          `json:"unique_email_id"`
	EmailType     string          `json:"email_type"`
	Stats         MemberStats     `json:"stats"`
	MemberRating  int             `json:"member_rating"`
	LastChanged   string          `json:"last_changed"`
	EmailClient   string          `json:"email_client"`
	LastNote      MemberNoteShort `json:"last_note"`

	api *API
}

func (mem Member) CanMakeRequest() error {
	if mem.ListID == "" {
		return errors.New("No ListID provided")
	}

	if mem.ID == "" {
		return errors.New("No ID provided")
	}

	return nil
}

type MemberStats struct {
	AvgOpenRate  float64 `json:"avg_open_rate"`
	AvgClickRate float64 `json:"avg_click_rate"`
}

type MemberLocation struct {
	Latitude    float64 `json:"latitude"`
	Longitude   float64 `json:"longitude"`
	GMTOffset   int     `json:"gmtoff"`
	DSTOffset   int     `json:"dstoff"`
	CountryCode string  `json:"country_code"`
	Timezone    string  `json:"timezone"`
}

type MarketingPermissions []MarketingPermission

type MarketingPermission struct {
	MarketingPermissionID string `json:"marketing_permission_id"`
	Text                  string `json:"text"`
	Enabled               bool   `json:"enabled"`
}

type MemberNoteShort struct {
	ID        int    `json:"note_id"`
	CreatedAt string `json:"created_at"`
	CreatedBy string `json:"created_by"`
	Note      string `json:"note"`
}

type MemberTag struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func (list ListResponse) GetMembers(params *InterestCategoriesQueryParams) (*ListOfMembers, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(members_path, list.ID)
	response := new(ListOfMembers)

	err := list.api.Request("GET", endpoint, params, nil, response)
	if err != nil {
		return nil, err
	}

	for _, m := range response.Members {
		m.api = list.api
	}

	return response, nil
}

func (list ListResponse) GetMember(id string, params *BasicQueryParams) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("GET", endpoint, params, nil, response)
}

func (list ListResponse) CreateMember(body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(members_path, list.ID)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("POST", endpoint, nil, body, response)
}

func (list ListResponse) UpdateMember(id string, body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("PATCH", endpoint, nil, body, response)
}

func (list ListResponse) AddOrUpdateMember(id string, body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("PUT", endpoint, nil, body, response)
}

func (list ListResponse) DeleteMember(id string) (bool, error) {
	if err := list.CanMakeRequest(); err != nil {
		return false, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	return list.api.RequestOk("DELETE", endpoint)
}

// ------------------------------------------------------------------------------------------------
// Activity
// ------------------------------------------------------------------------------------------------

type ListOfMemberActivity struct {
	baseList

	EmailID  string     `json:"email_id"`
	ListID   string     `json:"list_id"`
	Activity []Activity `json:"activity"`
}

type MemberActivity struct {
	Action         string `json:"action"`
	Timestamp      string `json:"timestamp"`
	URL            string `json:"url"`
	Type           string `json:"type"`
	CampaignID     string `json:"campaign_id"`
	Title          string `json:"title"`
	ParentCampaign string `json:"parent_campaign"`
}

func (mem Member) GetActivity(params *BasicQueryParams) (*ListOfMemberActivity, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_activity_path, mem.ListID, mem.ID)
	response := new(ListOfMemberActivity)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

// ------------------------------------------------------------------------------------------------
// Goals
// ------------------------------------------------------------------------------------------------

type ListOfMemberGoals struct {
	baseList

	EmailID string       `json:"email_id"`
	ListID  string       `json:"list_id"`
	Goals   []MemberGoal `json:"goals"`
}

type MemberGoal struct {
	ID            int    `json:"goal_id"`
	Event         string `json:"event"`
	LastVisitedAt string `json:"last_visited_at"`
	Data          string `json:"data"`
}

func (mem Member) GetGoals(params *BasicQueryParams) (*ListOfMemberGoals, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_goals_path, mem.ListID, mem.ID)
	response := new(ListOfMemberGoals)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

// ------------------------------------------------------------------------------------------------
// NOTES
// ------------------------------------------------------------------------------------------------

type ListOfMemberNotes struct {
	baseList

	EmailID string           `json:"email_id"`
	ListID  string           `json:"list_id"`
	Notes   []MemberNoteLong `json:"notes"`
}

type MemberNoteLong struct {
	ID        int    `json:"id"`
	CreatedAt string `json:"created_at"`
	CreatedBy string `json:"created_by"`
	UpdatedAt string `json:"updated_at"`
	Note      string `json:"note"`
	ListID    string `json:"list_id"`
	EmailID   string `json:"email_id"`

	withLinks
}

func (mem Member) GetNotes(params *ExtendedQueryParams) (*ListOfMemberNotes, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_notes_path, mem.ListID, mem.ID)
	response := new(ListOfMemberNotes)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

func (mem Member) CreateNote(msg string) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_notes_path, mem.ListID, mem.ID)
	response := new(MemberNoteLong)

	body := struct{ Note string }{
		Note: msg,
	}

	return response, mem.api.Request("POST", endpoint, nil, &body, response)
}

func (mem Member) UpdateNote(id, msg string) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	response := new(MemberNoteLong)

	body := struct{ Note string }{
		Note: msg,
	}

	return response, mem.api.Request("PATCH", endpoint, nil, &body, response)
}

func (mem Member) GetNote(id string, params *BasicQueryParams) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	response := new(MemberNoteLong)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

func (mem Member) DeleteNote(id string) (bool, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return false, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	return mem.api.RequestOk("DELETE", endpoint)
}

// ------------------------------------------------------------------------------------------------
// TAGS
// ------------------------------------------------------------------------------------------------

type ListOfMemberTags struct {
	baseList

	Tags []MemberTagLong `json:"tags"`
}

type MemberTagLong struct {
	ID        int     `json:"id"`
	Name      string  `json:"name"`
	DataAdded *string `json:"date_added,omitempty"`
	Status    string  `json:"status,omitempty"`

	withLinks
}

func (mem Member) GetTags(params *ExtendedQueryParams) (*ListOfMemberTags, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_tags_path, mem.ListID, mem.ID)
	response := new(ListOfMemberTags)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

func (mem Member) UpdateTags(tags []MemberTagLong) (*ListOfMemberTags, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_tags_path, mem.ListID, mem.ID)
	response := new(ListOfMemberTags)

	body := struct{ tags []MemberTagLong }{
		tags: tags,
	}

	return response, mem.api.Request("POST", endpoint, nil, &body, response)
}
<|endoftext|>