<commit_before>// Streams methods of the twitch api.
// https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md

package twitch

import (
	"strconv"
	"net/url"
)

// used with GET /streams/:channel/
type SChannelS struct {
	Stream *StreamS `json:"stream,omitempty"`
	Links  *LinksS  `json:"_links,omitempty"`
}

// used with GET /streams
type StreamsS struct {
	Streams []*StreamS `json:"streams,omitempty"`
	Links   *LinksS    `json:"_links,omitempty"`
}

// used with GET /streams/featured
type FeaturedS struct {
	Featured []*FStreamS `json:"featured,omitempty"`
	Links    *LinksS     `json:"_links,omitempty"`
}

// used with GET /streams/summary
type SummaryS struct {
	Viewers  int `json:"viewers,omitempty"`
	Channels int `json:"channels,omitempty"`
}

type StreamsMethod struct {
	client *Client
}

// Returns a stream object if online.
func (s *StreamsMethod) Channel(name string) (*SChannelS, error) {
	rel := "streams/" + name

	stream := new(SChannelS)
	_, err := s.client.Get(rel, stream)
	return stream, err
}

// Returns a list of stream objects according to optional parameters.
func (s *StreamsMethod) List(opt *ListOptions) (*StreamsS, error) {
	rel := "streams"
	if opt != nil {
		p := url.Values{
			"game":       []string{opt.Game},
			"channel":    []string{opt.Channel},
			"limit":      []string{strconv.Itoa(opt.Limit)},
			"offset":     []string{strconv.Itoa(opt.Offset)},
			"embeddable": []string{strconv.FormatBool(opt.Embeddable)},
			"hls":        []string{strconv.FormatBool(opt.Hls)},
			"client_id":  []string{opt.ClientId},
		}
		rel += "?" + p.Encode()
	}

	streams := new(StreamsS)
	_, err := s.client.Get(rel, streams)
	return streams, err
}

// Returns a list of featured (promoted) stream objects.
func (s *StreamsMethod) Featured(opt *ListOptions) (*FeaturedS, error) {
	rel := "streams/featured"
	if opt != nil {
		p := url.Values{
			"limit":  []string{strconv.Itoa(opt.Limit)},
			"offset": []string{strconv.Itoa(opt.Offset)},
			"hls":    []string{strconv.FormatBool(opt.Hls)},
		}
		rel += "?" + p.Encode()
	}

	featured := new(FeaturedS)
	_, err := s.client.Get(rel, featured)
	return featured, err
}

// Returns a summary of current streams.
func (s *StreamsMethod) Summary(opt *ListOptions) (*SummaryS, error) {
	rel := "streams/summary"
	if opt != nil {
		p := url.Values{"game":  []string{strconv.Itoa(opt.Game)}}
		rel += "?" + p.Encode()
	}

	summary := new(SummaryS)
	_, err := s.client.Get(rel, summary)
	return summary, err
}

// TODO GET /streams/followed
<commit_msg>Fixed options in Summary.<commit_after>// Streams methods of the twitch api.
// https://github.com/justintv/Twitch-API/blob/master/v3_resources/streams.md

package twitch

import (
	"strconv"
	"net/url"
)

// used with GET /streams/:channel/
type SChannelS struct {
	Stream *StreamS `json:"stream,omitempty"`
	Links  *LinksS  `json:"_links,omitempty"`
}

// used with GET /streams
type StreamsS struct {
	Streams []*StreamS `json:"streams,omitempty"`
	Links   *LinksS    `json:"_links,omitempty"`
}

// used with GET /streams/featured
type FeaturedS struct {
	Featured []*FStreamS `json:"featured,omitempty"`
	Links    *LinksS     `json:"_links,omitempty"`
}

// used with GET /streams/summary
type SummaryS struct {
	Viewers  int `json:"viewers,omitempty"`
	Channels int `json:"channels,omitempty"`
}

type StreamsMethod struct {
	client *Client
}

// Returns a stream object if online.
func (s *StreamsMethod) Channel(name string) (*SChannelS, error) {
	rel := "streams/" + name

	stream := new(SChannelS)
	_, err := s.client.Get(rel, stream)
	return stream, err
}

// Returns a list of stream objects according to optional parameters.
func (s *StreamsMethod) List(opt *ListOptions) (*StreamsS, error) {
	rel := "streams"
	if opt != nil {
		p := url.Values{
			"game":       []string{opt.Game},
			"channel":    []string{opt.Channel},
			"limit":      []string{strconv.Itoa(opt.Limit)},
			"offset":     []string{strconv.Itoa(opt.Offset)},
			"embeddable": []string{strconv.FormatBool(opt.Embeddable)},
			"hls":        []string{strconv.FormatBool(opt.Hls)},
			"client_id":  []string{opt.ClientId},
		}
		rel += "?" + p.Encode()
	}

	streams := new(StreamsS)
	_, err := s.client.Get(rel, streams)
	return streams, err
}

// Returns a list of featured (promoted) stream objects.
func (s *StreamsMethod) Featured(opt *ListOptions) (*FeaturedS, error) {
	rel := "streams/featured"
	if opt != nil {
		p := url.Values{
			"limit":  []string{strconv.Itoa(opt.Limit)},
			"offset": []string{strconv.Itoa(opt.Offset)},
			"hls":    []string{strconv.FormatBool(opt.Hls)},
		}
		rel += "?" + p.Encode()
	}

	featured := new(FeaturedS)
	_, err := s.client.Get(rel, featured)
	return featured, err
}

// Returns a summary of current streams.
func (s *StreamsMethod) Summary(opt *ListOptions) (*SummaryS, error) {
	rel := "streams/summary"
	if opt != nil {
		p := url.Values{"game":  []string{opt.Game}}
		rel += "?" + p.Encode()
	}

	summary := new(SummaryS)
	_, err := s.client.Get(rel, summary)
	return summary, err
}

// TODO GET /streams/followed
<|endoftext|>


<commit_before>/*
 * MumbleDJ
 * By Matthieu Grieger
 * strings.go
 * Copyright (c) 2014, 2015 Matthieu Grieger (MIT License)
 */

package main

// Message shown to users when the bot has an invalid API key.
const INVALID_API_KEY = "MumbleDJ does not have a valid %s API key."

// Message shown to users when they do not have permission to execute a command.
const NO_PERMISSION_MSG = "You do not have permission to execute that command."

// Message shown to users when they try to add a playlist to the queue and do not have permission to do so.
const NO_PLAYLIST_PERMISSION_MSG = "You do not have permission to add playlists to the queue."

// Message shown to users when they try to execute a command that doesn't exist.
const COMMAND_DOESNT_EXIST_MSG = "The command you entered does not exist."

// Message shown to users when they try to move the bot to a non-existant channel.
const CHANNEL_DOES_NOT_EXIST_MSG = "The channel you specified does not exist."

// Message shown to users when they attempt to add an invalid URL to the queue.
const INVALID_URL_MSG = "The URL you submitted does not match the required format."

// Message shown to users when they attempt to add a video that's too long
const TRACK_TOO_LONG_MSG = "The %s you submitted exceeds the duration allowed by the server."

// Message shown to users when they attempt to perform an action on a song when
// no song is playing.
const NO_MUSIC_PLAYING_MSG = "There is no music playing at the moment."

// Message shown to users when they attempt to skip a playlist when there is no playlist playing.
const NO_PLAYLIST_PLAYING_MSG = "There is no playlist playing at the moment."

// Message shown to users when they attempt to use the nextsong command when there is no song coming up.
const NO_SONG_NEXT_MSG = "There are no songs queued at the moment."

// Message shown to users when they issue a command that requires an argument and one was not supplied.
const NO_ARGUMENT_MSG = "The command you issued requires an argument and you did not provide one."

// Message shown to users when they try to change the volume to a value outside the volume range.
const NOT_IN_VOLUME_RANGE_MSG = "Out of range. The volume must be between %f and %f."

// Message shown to user when a successful configuration reload finishes.
const CONFIG_RELOAD_SUCCESS_MSG = "The configuration has been successfully reloaded."

// Message shown to users when an admin skips a song.
const ADMIN_SONG_SKIP_MSG = "An admin has decided to skip the current song."

// Message shown to users when an admin skips a playlist.
const ADMIN_PLAYLIST_SKIP_MSG = "An admin has decided to skip the current playlist."

// Message shown to users when the audio for a video could not be downloaded.
const AUDIO_FAIL_MSG = "The audio download for this video failed. %s has likely not generated the audio files for this %s yet. Skipping to the next song!"

// Message shown to users when they supply an URL that does not contain a valid ID.
const INVALID_ID_MSG = "The %s URL you supplied did not contain a valid ID."

// Message shown to user when they successfully update the bot's comment.
const COMMENT_UPDATED_MSG = "The comment for the bot has successfully been updated."

// Message shown to user when they request to see the number of songs cached on disk.
const NUM_CACHED_MSG = "There are currently %d songs cached on disk."

// Message shown to user when they request to see the total size of the cache.
const CACHE_SIZE_MSG = "The cache is currently %g MB in size."

// Message shown to user when they attempt to issue a cache-related command when caching is not enabled.
const CACHE_NOT_ENABLED_MSG = "The cache is not currently enabled."

// Message shown to user when they attempt to shuffle the queue and it has less than 2 elements.
const CANT_SHUFFLE_MSG = "Can't shuffle the queue if there is less than 2 songs."

// Message shown to users when the songqueue has been successfully shuffled.
const SHUFFLE_SUCCESS_MSG = "The current songqueue has been successfully shuffled by %s (starting from next song)."

// Message shown to users when automatic shuffle is activated
const SHUFFLE_ON_MESSAGE = "%s has turned automatic shuffle on."

// Message shown to users when automatic shuffle is deactivated
const SHUFFLE_OFF_MESSAGE = "%s has turned automatic shuffle off."

// Message shown to user when they attempt to enable automatic shuffle while it's already activated
const SHUFFLE_ACTIVATED_ERROR_MESSAGE = "Automatic shuffle is already activated."

// Message shown to user when they attempt to disable automatic shuffle while it's already deactivated
const SHUFFLE_DEACTIVATED_ERROR_MESSAGE = "Automatic shuffle is already deactivated."

// Message shown to channel when a song is added to the queue by a user.
const SONG_ADDED_HTML = `
	%s has added "%s" to the queue.
`

// Message shown to channel when a playlist is added to the queue by a user.
const PLAYLIST_ADDED_HTML = `
	%s has added the playlist "%s" to the queue.
`

// Message shown to channel when a song is added to the queue by a user after the current song.
const NEXT_SONG_ADDED_HTML = `
	%s has added "%s" to the queue after the current song.
`

// Message shown to channel when a playlist is added to the queue by a user after the current song.
const NEXT_PLAYLIST_ADDED_HTML = `
	%s has added the playlist "%s" to the queue after the current song.
`

// Message shown to channel when a song has been skipped.
const SONG_SKIPPED_HTML = `
	The number of votes required for a skip has been met. Skipping song!
`

// Message shown to channel when a playlist has been skipped.
const PLAYLIST_SKIPPED_HTML = `
	The number of votes required for a skip has been met. Skipping playlist!
`

// Message shown to display bot commands.
const HELP_HTML = `

	User Commands:

!help - Displays this help.

!add - Adds songs/playlists to queue.

!volume - Either tells you the current volume or sets it to a new volume.

!skip - Casts a vote to skip the current song

 !skipplaylist - Casts a vote to skip over the current playlist.

!numsongs - Shows how many songs are in queue.

!listsongs - Lists the songs in queue.

!nextsong - Shows the title and submitter of the next queue item if it exists.

!currentsong - Shows the title and submitter of the song currently playing.

Admin Commands:

!addnext - Adds songs/playlists to queue after the current song.

!reset - An admin command that resets the song queue.

!forceskip - An admin command that forces a song skip.

!forceskipplaylist - An admin command that forces a playlist skip.

!shuffle - An admin command that shuffles the current queue.

!shuffleon - An admin command that enables auto shuffling.

!shuffleoff - An admin command that disables auto shuffling.

!move - Moves MumbleDJ into channel if it exists.

!reload - Reloads mumbledj.gcfg configuration settings.

!setcomment - Sets the comment for the bot.

!numcached
 - Outputs the number of songs cached on disk.

!cachesize
 - Outputs the total file size of the cache in MB.

!kill - Safely cleans the bot environment and disconnects from the server.

`

// Message shown to users when they ask for the current volume (volume command without argument)
const CUR_VOLUME_HTML = `
	The current volume is %.2f.
`

// Message shown to users when another user votes to skip the current song.
const SKIP_ADDED_HTML = `
	%s has voted to skip the current song.
`

// Message shown to users when the submitter of a song decides to skip their song.
const SUBMITTER_SKIP_HTML = `
	The current song has been skipped by %s, the submitter.
`

// Message shown to users when another user votes to skip the current playlist.
const PLAYLIST_SKIP_ADDED_HTML = `
	%s has voted to skip the current playlist.
`

// Message shown to users when the submitter of a song decides to skip their song.
const PLAYLIST_SUBMITTER_SKIP_HTML = `
	The current playlist has been skipped by %s, the submitter.
`

// Message shown to users when they successfully change the volume.
const VOLUME_SUCCESS_HTML = `
	%s has changed the volume to %.2f.
`

// Message shown to users when a user successfully resets the SongQueue.
const QUEUE_RESET_HTML = `
	%s has cleared the song queue.
`

// Message shown to users when a user asks how many songs are in the queue.
const NUM_SONGS_HTML = `
	There are currently %d song(s) in the queue.
`

// Message shown to users when they issue the nextsong command.
const NEXT_SONG_HTML = `
	The next song in the queue is "%s", added by %s.
`

// Message shown to users when they issue the currentsong command.
const CURRENT_SONG_HTML = `
	The song currently playing is "%s", added by %s.
`

// Message shown to users when the currentsong command is issued when a song from a
// playlist is playing.
const CURRENT_SONG_PLAYLIST_HTML = `
	The %s currently playing is "%s", added %s from the %s "%s".
`

// Message shown to user when the listsongs command is issued
const SONG_LIST_HTML = `

%d: "%s", added by %s</b<.

`
<commit_msg>Typo with the `</b>` tag on SONG_LIST_HTML<commit_after>/*
 * MumbleDJ
 * By Matthieu Grieger
 * strings.go
 * Copyright (c) 2014, 2015 Matthieu Grieger (MIT License)
 */

package main

// Message shown to users when the bot has an invalid API key.
const INVALID_API_KEY = "MumbleDJ does not have a valid %s API key."

// Message shown to users when they do not have permission to execute a command.
const NO_PERMISSION_MSG = "You do not have permission to execute that command."

// Message shown to users when they try to add a playlist to the queue and do not have permission to do so.
const NO_PLAYLIST_PERMISSION_MSG = "You do not have permission to add playlists to the queue."

// Message shown to users when they try to execute a command that doesn't exist.
const COMMAND_DOESNT_EXIST_MSG = "The command you entered does not exist."

// Message shown to users when they try to move the bot to a non-existant channel.
const CHANNEL_DOES_NOT_EXIST_MSG = "The channel you specified does not exist."

// Message shown to users when they attempt to add an invalid URL to the queue.
const INVALID_URL_MSG = "The URL you submitted does not match the required format."

// Message shown to users when they attempt to add a video that's too long
const TRACK_TOO_LONG_MSG = "The %s you submitted exceeds the duration allowed by the server."

// Message shown to users when they attempt to perform an action on a song when
// no song is playing.
const NO_MUSIC_PLAYING_MSG = "There is no music playing at the moment."

// Message shown to users when they attempt to skip a playlist when there is no playlist playing.
const NO_PLAYLIST_PLAYING_MSG = "There is no playlist playing at the moment."

// Message shown to users when they attempt to use the nextsong command when there is no song coming up.
const NO_SONG_NEXT_MSG = "There are no songs queued at the moment."

// Message shown to users when they issue a command that requires an argument and one was not supplied.
const NO_ARGUMENT_MSG = "The command you issued requires an argument and you did not provide one."

// Message shown to users when they try to change the volume to a value outside the volume range.
const NOT_IN_VOLUME_RANGE_MSG = "Out of range. The volume must be between %f and %f."

// Message shown to user when a successful configuration reload finishes.
const CONFIG_RELOAD_SUCCESS_MSG = "The configuration has been successfully reloaded."

// Message shown to users when an admin skips a song.
const ADMIN_SONG_SKIP_MSG = "An admin has decided to skip the current song."

// Message shown to users when an admin skips a playlist.
const ADMIN_PLAYLIST_SKIP_MSG = "An admin has decided to skip the current playlist."

// Message shown to users when the audio for a video could not be downloaded.
const AUDIO_FAIL_MSG = "The audio download for this video failed. %s has likely not generated the audio files for this %s yet. Skipping to the next song!"

// Message shown to users when they supply an URL that does not contain a valid ID.
const INVALID_ID_MSG = "The %s URL you supplied did not contain a valid ID."

// Message shown to user when they successfully update the bot's comment.
const COMMENT_UPDATED_MSG = "The comment for the bot has successfully been updated."

// Message shown to user when they request to see the number of songs cached on disk.
const NUM_CACHED_MSG = "There are currently %d songs cached on disk."

// Message shown to user when they request to see the total size of the cache.
const CACHE_SIZE_MSG = "The cache is currently %g MB in size."

// Message shown to user when they attempt to issue a cache-related command when caching is not enabled.
const CACHE_NOT_ENABLED_MSG = "The cache is not currently enabled."

// Message shown to user when they attempt to shuffle the queue and it has less than 2 elements.
const CANT_SHUFFLE_MSG = "Can't shuffle the queue if there is less than 2 songs."

// Message shown to users when the songqueue has been successfully shuffled.
const SHUFFLE_SUCCESS_MSG = "The current songqueue has been successfully shuffled by %s (starting from next song)."

// Message shown to users when automatic shuffle is activated
const SHUFFLE_ON_MESSAGE = "%s has turned automatic shuffle on."

// Message shown to users when automatic shuffle is deactivated
const SHUFFLE_OFF_MESSAGE = "%s has turned automatic shuffle off."

// Message shown to user when they attempt to enable automatic shuffle while it's already activated
const SHUFFLE_ACTIVATED_ERROR_MESSAGE = "Automatic shuffle is already activated."

// Message shown to user when they attempt to disable automatic shuffle while it's already deactivated
const SHUFFLE_DEACTIVATED_ERROR_MESSAGE = "Automatic shuffle is already deactivated."

// Message shown to channel when a song is added to the queue by a user.
const SONG_ADDED_HTML = `
	%s has added "%s" to the queue.
`

// Message shown to channel when a playlist is added to the queue by a user.
const PLAYLIST_ADDED_HTML = `
	%s has added the playlist "%s" to the queue.
`

// Message shown to channel when a song is added to the queue by a user after the current song.
const NEXT_SONG_ADDED_HTML = `
	%s has added "%s" to the queue after the current song.
`

// Message shown to channel when a playlist is added to the queue by a user after the current song.
const NEXT_PLAYLIST_ADDED_HTML = `
	%s has added the playlist "%s" to the queue after the current song.
`

// Message shown to channel when a song has been skipped.
const SONG_SKIPPED_HTML = `
	The number of votes required for a skip has been met. Skipping song!
`

// Message shown to channel when a playlist has been skipped.
const PLAYLIST_SKIPPED_HTML = `
	The number of votes required for a skip has been met. Skipping playlist!
`

// Message shown to display bot commands.
const HELP_HTML = `

	User Commands:

!help - Displays this help.

!add - Adds songs/playlists to queue.

!volume - Either tells you the current volume or sets it to a new volume.

!skip - Casts a vote to skip the current song

 !skipplaylist - Casts a vote to skip over the current playlist.

!numsongs - Shows how many songs are in queue.

!listsongs - Lists the songs in queue.

!nextsong - Shows the title and submitter of the next queue item if it exists.

!currentsong - Shows the title and submitter of the song currently playing.

Admin Commands:

!addnext - Adds songs/playlists to queue after the current song.

!reset - An admin command that resets the song queue.

!forceskip - An admin command that forces a song skip.

!forceskipplaylist - An admin command that forces a playlist skip.

!shuffle - An admin command that shuffles the current queue.

!shuffleon - An admin command that enables auto shuffling.

!shuffleoff - An admin command that disables auto shuffling.

!move - Moves MumbleDJ into channel if it exists.

!reload - Reloads mumbledj.gcfg configuration settings.

!setcomment - Sets the comment for the bot.

!numcached
 - Outputs the number of songs cached on disk.

!cachesize
 - Outputs the total file size of the cache in MB.

!kill - Safely cleans the bot environment and disconnects from the server.

`

// Message shown to users when they ask for the current volume (volume command without argument)
const CUR_VOLUME_HTML = `
	The current volume is %.2f.
`

// Message shown to users when another user votes to skip the current song.
const SKIP_ADDED_HTML = `
	%s has voted to skip the current song.
`

// Message shown to users when the submitter of a song decides to skip their song.
const SUBMITTER_SKIP_HTML = `
	The current song has been skipped by %s, the submitter.
`

// Message shown to users when another user votes to skip the current playlist.
const PLAYLIST_SKIP_ADDED_HTML = `
	%s has voted to skip the current playlist.
`

// Message shown to users when the submitter of a song decides to skip their song.
const PLAYLIST_SUBMITTER_SKIP_HTML = `
	The current playlist has been skipped by %s, the submitter.
`

// Message shown to users when they successfully change the volume.
const VOLUME_SUCCESS_HTML = `
	%s has changed the volume to %.2f.
`

// Message shown to users when a user successfully resets the SongQueue.
const QUEUE_RESET_HTML = `
	%s has cleared the song queue.
`

// Message shown to users when a user asks how many songs are in the queue.
const NUM_SONGS_HTML = `
	There are currently %d song(s) in the queue.
`

// Message shown to users when they issue the nextsong command.
const NEXT_SONG_HTML = `
	The next song in the queue is "%s", added by %s.
`

// Message shown to users when they issue the currentsong command.
const CURRENT_SONG_HTML = `
	The song currently playing is "%s", added by %s.
`

// Message shown to users when the currentsong command is issued when a song from a
// playlist is playing.
const CURRENT_SONG_PLAYLIST_HTML = `
	The %s currently playing is "%s", added %s from the %s "%s".
`

// Message shown to user when the listsongs command is issued
const SONG_LIST_HTML = `

%d: "%s", added by %s.

`
<|endoftext|>


<commit_before>// +build unit

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 task

import (
	"reflect"
	"testing"
)

func taskN(n int) *Task {
	return &Task{
		Arn: string(n),
	}
}

func TestRemoveFromTaskArray(t *testing.T) {
	arr := []*Task{taskN(1), taskN(2), taskN(3)}

	arr2 := RemoveFromTaskArray(arr, -1)
	if !reflect.DeepEqual(arr, arr2) {
		t.Error("Index -1 out of bounds altered arr")
	}

	arr2 = RemoveFromTaskArray(arr, 3)
	if !reflect.DeepEqual(arr, arr2) {
		t.Error("Index 3 out of bounds altered arr")
	}

	arr = RemoveFromTaskArray(arr, 2)
	if !reflect.DeepEqual(arr, []*Task{taskN(1), taskN(2)}) {
		t.Error("Last element not removed")
	}
	arr = RemoveFromTaskArray(arr, 0)
	if !reflect.DeepEqual(arr, []*Task{taskN(2)}) {
		t.Error("First element not removed")
	}
	arr = RemoveFromTaskArray(arr, 0)
	if !reflect.DeepEqual(arr, []*Task{}) {
		t.Error("First element not removed")
	}
	arr = RemoveFromTaskArray(arr, 0)
	if !reflect.DeepEqual(arr, []*Task{}) {
		t.Error("Removing from empty arr changed it")
	}
}
<commit_msg>Refactor agent/api/task/util_test.go to avoid string conversion error with go 1.15<commit_after>// +build unit

// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 task

import (
	"fmt"
	"reflect"
	"testing"
)

func taskN(n int) *Task {
	return &Task{
		Arn: fmt.Sprint(n),
	}
}

func TestRemoveFromTaskArray(t *testing.T) {
	arr := []*Task{taskN(1), taskN(2), taskN(3)}

	arr2 := RemoveFromTaskArray(arr, -1)
	if !reflect.DeepEqual(arr, arr2) {
		t.Error("Index -1 out of bounds altered arr")
	}

	arr2 = RemoveFromTaskArray(arr, 3)
	if !reflect.DeepEqual(arr, arr2) {
		t.Error("Index 3 out of bounds altered arr")
	}

	arr = RemoveFromTaskArray(arr, 2)
	if !reflect.DeepEqual(arr, []*Task{taskN(1), taskN(2)}) {
		t.Error("Last element not removed")
	}
	arr = RemoveFromTaskArray(arr, 0)
	if !reflect.DeepEqual(arr, []*Task{taskN(2)}) {
		t.Error("First element not removed")
	}
	arr = RemoveFromTaskArray(arr, 0)
	if !reflect.DeepEqual(arr, []*Task{}) {
		t.Error("First element not removed")
	}
	arr = RemoveFromTaskArray(arr, 0)
	if !reflect.DeepEqual(arr, []*Task{}) {
		t.Error("Removing from empty arr changed it")
	}
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 conn

import (
	"bytes"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"net/url"
	"strings"
	"testing"
)

func TestPostBody(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type PostBodyTest struct {
}

func init() { RegisterTestSuite(&PostBodyTest{}) }

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *PostBodyTest) NoParameters() {
	req := Request{
	}

	body := assemblePostBody(req)

	ExpectEq("", body)
}

func (t *PostBodyTest) OneParameter() {
	req := Request{
		"taco": "burrito",
	}

	body := assemblePostBody(req)

	ExpectEq("taco=burrito", body)
}

func (t *PostBodyTest) MultipleParameters() {
	req := Request{
		"taco": "burrito",
		"enchilada": "queso",
		"nachos": "carnitas",
	}

	body := assemblePostBody(req)
	components := strings.Split(string(body), "&")

	AssertEq(3, len(components), "Components: %v", components)
	ExpectThat(components, Contains("taco=burrito"))
	ExpectThat(components, Contains("enchilada=queso"))
	ExpectThat(components, Contains("nachos=carnitas"))
}

func (t *PostBodyTest) EmptyParameterName() {
	req := Request{
		"": "burrito",
		"enchilada": "queso",
	}

	body := assemblePostBody(req)
	components := strings.Split(string(body), "&")

	AssertEq(2, len(components), "Components: %v", components)
	ExpectThat(components, Contains("=burrito"))
	ExpectThat(components, Contains("enchilada=queso"))
}

func (t *PostBodyTest) UnreservedCharacters() {
	req := Request{
		"abcdefghijklmnopqrstuvwxyz": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
		"0123456789": "-_.~",
	}

	body := assemblePostBody(req)

	ExpectThat(body, HasSubstr("abcdefghijklmnopqrstuvwxyz"))
	ExpectThat(body, HasSubstr("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
	ExpectThat(body, HasSubstr("0123456789"))
	ExpectThat(body, HasSubstr("-_.~"))
}

func (t *PostBodyTest) StructuralCharacters() {
	req := Request{
		":/?#[]@": "!$&'()*+,;=",
	}

	body := assemblePostBody(req)

	ExpectThat(body, HasSubstr("TODO"))
}

func (t *PostBodyTest) PercentCharacter() {
	req := Request{
		"a%b": "c%d",
	}

	body := assemblePostBody(req)

	ExpectThat(body, HasSubstr("TODO"))
}

func (t *PostBodyTest) SpaceAndPlus() {
	req := Request{
		"b+a z": "q+u x",
	}

	body := assemblePostBody(req)
	ExpectEq("b%2B%20z=q%2Bu%20x", body)
}

func (t *PostBodyTest) KoreanCharacters() {
	req := Request{
		"음식": "타코",
	}

	body := assemblePostBody(req)
	ExpectEq("TODO", body)
}

func (t *PostBodyTest) ParameterOrdering() {
	// Sanity check: ordering of Korean.
	AssertEq(-1, bytes.Compare([]byte("음"), []byte("타")))

	// Sanity check: ordering of unescaped strings reversed by escaping. (We
	// should order by parameter name, not be escaped parameter name.)
	AssertLt("f", "|")
	AssertGt(url.QueryEscape("f"), url.QueryEscape("|"))

	// Request
	req := Request{
		// Easy cases
		"bar": "asd",
		"qux": "asd",
		"aaa": "asd",

		// Korean ordering
		"타코": "bar",
		"음식": "foo",

		// Order before escaping
		"foo": "asd",
		"|": "asd",
	}

	body := assemblePostBody(req)
	components := strings.Split(string(body), "&")

	ExpectThat(
		components,
		ElementsAre(
			HasSubstr("aaa="),
			HasSubstr("bar="),
			HasSubstr("foo="),
			HasSubstr("qux="),
			HasSubstr(url.QueryEscape("|") + "="),
			HasSubstr(url.QueryEscape("음식") + "="),
			HasSubstr(url.QueryEscape("타코") + "=")))
}
<commit_msg>Fixed a test bug.<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 conn

import (
	"bytes"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"net/url"
	"strings"
	"testing"
)

func TestPostBody(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

type PostBodyTest struct {
}

func init() { RegisterTestSuite(&PostBodyTest{}) }

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *PostBodyTest) NoParameters() {
	req := Request{
	}

	body := assemblePostBody(req)

	ExpectEq("", body)
}

func (t *PostBodyTest) OneParameter() {
	req := Request{
		"taco": "burrito",
	}

	body := assemblePostBody(req)

	ExpectEq("taco=burrito", body)
}

func (t *PostBodyTest) MultipleParameters() {
	req := Request{
		"taco": "burrito",
		"enchilada": "queso",
		"nachos": "carnitas",
	}

	body := assemblePostBody(req)
	components := strings.Split(string(body), "&")

	AssertEq(3, len(components), "Components: %v", components)
	ExpectThat(components, Contains("taco=burrito"))
	ExpectThat(components, Contains("enchilada=queso"))
	ExpectThat(components, Contains("nachos=carnitas"))
}

func (t *PostBodyTest) EmptyParameterName() {
	req := Request{
		"": "burrito",
		"enchilada": "queso",
	}

	body := assemblePostBody(req)
	components := strings.Split(string(body), "&")

	AssertEq(2, len(components), "Components: %v", components)
	ExpectThat(components, Contains("=burrito"))
	ExpectThat(components, Contains("enchilada=queso"))
}

func (t *PostBodyTest) UnreservedCharacters() {
	req := Request{
		"abcdefghijklmnopqrstuvwxyz": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
		"0123456789": "-_.~",
	}

	body := assemblePostBody(req)

	ExpectThat(body, HasSubstr("abcdefghijklmnopqrstuvwxyz"))
	ExpectThat(body, HasSubstr("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
	ExpectThat(body, HasSubstr("0123456789"))
	ExpectThat(body, HasSubstr("-_.~"))
}

func (t *PostBodyTest) StructuralCharacters() {
	req := Request{
		":/?#[]@": "!$&'()*+,;=",
	}

	body := assemblePostBody(req)

	ExpectThat(body, HasSubstr("TODO"))
}

func (t *PostBodyTest) PercentCharacter() {
	req := Request{
		"a%b": "c%d",
	}

	body := assemblePostBody(req)

	ExpectThat(body, HasSubstr("TODO"))
}

func (t *PostBodyTest) SpaceAndPlus() {
	req := Request{
		"b+a z": "q+u x",
	}

	body := assemblePostBody(req)
	ExpectEq("b%2Ba%20z=q%2Bu%20x", body)
}

func (t *PostBodyTest) KoreanCharacters() {
	req := Request{
		"음식": "타코",
	}

	body := assemblePostBody(req)
	ExpectEq("TODO", body)
}

func (t *PostBodyTest) ParameterOrdering() {
	// Sanity check: ordering of Korean.
	AssertEq(-1, bytes.Compare([]byte("음"), []byte("타")))

	// Sanity check: ordering of unescaped strings reversed by escaping. (We
	// should order by parameter name, not be escaped parameter name.)
	AssertLt("f", "|")
	AssertGt(url.QueryEscape("f"), url.QueryEscape("|"))

	// Request
	req := Request{
		// Easy cases
		"bar": "asd",
		"qux": "asd",
		"aaa": "asd",

		// Korean ordering
		"타코": "bar",
		"음식": "foo",

		// Order before escaping
		"foo": "asd",
		"|": "asd",
	}

	body := assemblePostBody(req)
	components := strings.Split(string(body), "&")

	ExpectThat(
		components,
		ElementsAre(
			HasSubstr("aaa="),
			HasSubstr("bar="),
			HasSubstr("foo="),
			HasSubstr("qux="),
			HasSubstr(url.QueryEscape("|") + "="),
			HasSubstr(url.QueryEscape("음식") + "="),
			HasSubstr(url.QueryEscape("타코") + "=")))
}
<|endoftext|>


<commit_before>package projects

import (
	"time"
)

//Project is a representation of a project within the Ion Channel system
type Project struct {
	ID             string    `json:"id"`
	Active         bool      `json:"active"`
	Aliases        []string  `json:"aliases"`
	Branch         string    `json:"branch"`
	ChatChannel    string    `json:"chat_channel"`
	CreatedAt      time.Time `json:"created_at"`
	DeployKey      string    `json:"deploy_key"`
	Description    string    `json:"description"`
	KeyFingerprint string    `json:"key_fingerprint"`
	Name           string    `json:"name"`
	RulesetID      string    `json:"ruleset_id"`
	Monitor        bool      `json:"should_monitor"`
	Source         string    `json:"source"`
	Tags           []string  `json:"tags"`
	TeamID         string    `json:"team_id"`
	Type           string    `json:"type"`
	UpdatedAt      time.Time `json:"updated_at"`
}
<commit_msg>make tags and aliases a struct<commit_after>package projects

import (
	"time"
)

//Project is a representation of a project within the Ion Channel system
type Project struct {
	ID      string `json:"id"`
	Active  bool   `json:"active"`
	Aliases []struct {
		ID        string    `json:"id"`
		Name      string    `json:"name"`
		CreatedAt time.Time `json:"created_at"`
		UpdatedAt time.Time `json:"updated_at"`
		Version   string    `json:"version"`
	} `json:"aliases"`
	Branch         string    `json:"branch"`
	ChatChannel    string    `json:"chat_channel"`
	CreatedAt      time.Time `json:"created_at"`
	DeployKey      string    `json:"deploy_key"`
	Description    string    `json:"description"`
	KeyFingerprint string    `json:"key_fingerprint"`
	Name           string    `json:"name"`
	RulesetID      string    `json:"ruleset_id"`
	Monitor        bool      `json:"should_monitor"`
	Source         string    `json:"source"`
	Tags           []struct {
		ID          string      `json:"id"`
		TeamID      string      `json:"team_id"`
		Name        string      `json:"name"`
		Description interface{} `json:"description"`
		CreatedAt   time.Time   `json:"created_at"`
		UpdatedAt   time.Time   `json:"updated_at"`
	} `json:"tags"`
	TeamID    string    `json:"team_id"`
	Type      string    `json:"type"`
	UpdatedAt time.Time `json:"updated_at"`
}
<|endoftext|>


<commit_before>package swarm

import (
	"golang.org/x/net/context"

	"github.com/docker/docker/api/client"
	"github.com/docker/docker/api/client/inspect"
	"github.com/docker/docker/cli"
	"github.com/spf13/cobra"
)

type inspectOptions struct {
	format string
	//	pretty  bool
}

func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
	var opts inspectOptions

	cmd := &cobra.Command{
		Use:   "inspect [OPTIONS]",
		Short: "Inspect the Swarm",
		Args:  cli.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			// if opts.pretty && len(opts.format) > 0 {
			//	return fmt.Errorf("--format is incompatible with human friendly format")
			// }
			return runInspect(dockerCli, opts)
		},
	}

	flags := cmd.Flags()
	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
	//flags.BoolVarP(&opts.pretty, "pretty", "h", false, "Print the information in a human friendly format.")
	return cmd
}

func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
	client := dockerCli.Client()
	ctx := context.Background()

	swarm, err := client.SwarmInspect(ctx)
	if err != nil {
		return err
	}

	getRef := func(_ string) (interface{}, []byte, error) {
		return swarm, nil, nil
	}

	//	if !opts.pretty {
	return inspect.Inspect(dockerCli.Out(), []string{""}, opts.format, getRef)
	//	}

	//return printHumanFriendly(dockerCli.Out(), opts.refs, getRef)
}
<commit_msg>Remove dead code for "inspect --pretty"<commit_after>package swarm

import (
	"golang.org/x/net/context"

	"github.com/docker/docker/api/client"
	"github.com/docker/docker/api/client/inspect"
	"github.com/docker/docker/cli"
	"github.com/spf13/cobra"
)

type inspectOptions struct {
	format string
}

func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
	var opts inspectOptions

	cmd := &cobra.Command{
		Use:   "inspect [OPTIONS]",
		Short: "Inspect the Swarm",
		Args:  cli.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			return runInspect(dockerCli, opts)
		},
	}

	flags := cmd.Flags()
	flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
	return cmd
}

func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
	client := dockerCli.Client()
	ctx := context.Background()

	swarm, err := client.SwarmInspect(ctx)
	if err != nil {
		return err
	}

	getRef := func(_ string) (interface{}, []byte, error) {
		return swarm, nil, nil
	}

	return inspect.Inspect(dockerCli.Out(), []string{""}, opts.format, getRef)
}
<|endoftext|>


<commit_before>package couchdb

import (
	"fmt"
	"strings"
)

// http://docs.couchdb.org/en/latest/intro/api.html#server
type Server struct {
	Couchdb string
	Uuid    string
	Vendor  struct {
		Version string
		Name    string
	}
	Version string
}

// http://docs.couchdb.org/en/latest/api/database/common.html#get--db
type DatabaseInfo struct {
	DbName             string `json:"db_name"`
	DocCount           int    `json:"doc_count"`
	DocDelCount        int    `json:"doc_del_count"`
	UpdateSeq          int    `json:"update_seq"`
	PurgeSeq           int    `json:"purge_seq"`
	CompactRunning     bool   `json:"compact_running"`
	DiskSize           int    `json:"disk_size"`
	DataSize           int    `json:"data_size"`
	InstanceStartTime  string `json:"instance_start_time"`
	DiskFormatVersion  int    `json:"disk_format_version"`
	CommittedUpdateSeq int    `json:"committed_update_seq"`
}

type DatabaseResponse struct {
	Ok bool
}

type Error struct {
	Method     string
	Url        string
	StatusCode int
	Type       string `json:"error"`
	Reason     string
}

func (e *Error) Error() string {
	return fmt.Sprintf(
		"CouchDB - %s %s, Status Code: %d, Error: %s, Reason: %s",
		e.Method,
		e.Url,
		e.StatusCode,
		e.Type,
		e.Reason,
	)
}

type CouchDoc interface {
	GetDocument() *Document
}

type Document struct {
	Id          string                `json:"_id,omitempty"`
	Rev         string                `json:"_rev,omitempty"`
	Attachments map[string]Attachment `json:"_attachments,omitempty"`
}

func (d *Document) GetDocument() *Document {
	return d
}

type DesignDocument struct {
	Document
	Language string                        `json:"language,omitempty"`
	Views    map[string]DesignDocumentView `json:"views,omitempty"`
}

func (dd DesignDocument) Name() string {
	return strings.TrimPrefix(dd.Id, "_design/")
}

type DesignDocumentView struct {
	Map    string `json:"map,omitempty"`
	Reduce string `json:"reduce,omitempty"`
}

// http://docs.couchdb.org/en/latest/api/document/common.html#creating-multiple-attachments
type Attachment struct {
	Follows     bool   `json:"follows"`
	ContentType string `json:"content_type"`
	Length      int64  `json:"length"`
}

type DocumentResponse struct {
	Ok  bool
	Id  string
	Rev string
}

// http://docs.couchdb.org/en/latest/api/server/common.html#active-tasks
type Task struct {
	ChangesDone  int `json:"changes_done"`
	Database     string
	Pid          string
	Progress     int
	StartedOn    int `json:"started_on"`
	Status       string
	Task         string
	TotalChanges int `json:"total_changes"`
	Type         string
	UpdatedOn    string `json:"updated_on"`
}

type QueryParameters struct {
	Conflicts       bool   `url:"conflicts,omitempty"`
	Descending      bool   `url:"descending,omitempty"`
	EndKey          string `url:"endkey,comma,omitempty"`
	EndKeyDocId     string `url:"end_key_doc_id,omitempty"`
	Group           bool   `url:"group,omitempty"`
	GroupLevel      int    `url:"group_level,omitempty"`
	IncludeDocs     bool   `url:"include_docs,omitempty"`
	Attachments     bool   `url:"attachments,omitempty"`
	AttEncodingInfo bool   `url:"att_encoding_info,omitempty"`
	InclusiveEnd    bool   `url:"inclusive_end,omitempty"`
	Key             string `url:"key,omitempty"`
	Limit           int    `url:"limit,omitempty"`
	Reduce          bool   `url:"reduce,omitempty"`
	Skip            int    `url:"skip,omitempty"`
	Stale           string `url:"stale,omitempty"`
	StartKey        string `url:"startkey,comma,omitempty"`
	StartKeyDocId   string `url:"startkey_docid,omitempty"`
	UpdateSeq       bool   `url:"update_seq,omitempty"`
}

type ViewResponse struct {
	Offset    int   `json:"offset,omitempty"`
	Rows      []Row `json:"rows,omitempty"`
	TotalRows int   `json:"total_rows,omitempty"`
	UpdateSeq int   `json:"update_seq,omitempty"`
}

type Row struct {
	Id    string                 `json:"id"`
	Key   interface{}            `json:"key"`
	Value interface{}            `json:"value,omitempty"`
	Doc   map[string]interface{} `json:"doc,omitempty"`
}

// http://docs.couchdb.org/en/latest/api/database/bulk-api.html#post--db-_bulk_docs
type BulkDoc struct {
	AllOrNothing bool          `json:"all_or_nothing,omitempty"`
	Docs         []interface{} `json:"docs"`
	NewEdits     bool          `json:"new_edits,omitempty"`
}

// http://docs.couchdb.org/en/latest/api/server/authn.html#cookie-authentication
type Credentials struct {
	Name     string `json:"name"`
	Password string `json:"password"`
}

type PostSessionResponse struct {
	Ok    bool
	Name  string
	Roles []string
}

type User struct {
	Document
	DerivedKey     string   `json:"derived_key,omitempty"`
	Name           string   `json:"name,omitempty"`
	Roles          []string `json:"roles"`
	Password       string   `json:"password,omitempty"`     // plain text password when creating the user
	PasswordSha    string   `json:"password_sha,omitempty"` // hashed password when requesting user information
	PasswordScheme string   `json:"password_scheme,omitempty"`
	Salt           string   `json:"salt,omitempty"`
	Type           string   `json:"type,omitempty"`
	Iterations     int      `json:"iterations,omitempty"`
}

func NewUser(name, password string, roles []string) User {
	user := User{
		Document: Document{
			Id: "org.couchdb.user:" + name,
		},
		DerivedKey:     "",
		Name:           name,
		Roles:          roles,
		Password:       password,
		PasswordSha:    "",
		PasswordScheme: "",
		Salt:           "",
		Type:           "user",
	}
	return user
}

type GetSessionResponse struct {
	Info struct {
		Authenticated          string   `json:"authenticated"`
		AuthenticationDb       string   `json:"authentication_db"`
		AuthenticationHandlers []string `json:"authentication_handlers"`
	} `json:"info"`
	Ok          bool `json:"ok"`
	UserContext struct {
		Db    string   `json:"db"`
		Name  string   `json:"name"`
		Roles []string `json:"roles"`
	} `json:"userCtx"`
}
<commit_msg>remove omitempty from query params to allow setting them explicitly<commit_after>package couchdb

import (
	"fmt"
	"strings"
)

// http://docs.couchdb.org/en/latest/intro/api.html#server
type Server struct {
	Couchdb string
	Uuid    string
	Vendor  struct {
		Version string
		Name    string
	}
	Version string
}

// http://docs.couchdb.org/en/latest/api/database/common.html#get--db
type DatabaseInfo struct {
	DbName             string `json:"db_name"`
	DocCount           int    `json:"doc_count"`
	DocDelCount        int    `json:"doc_del_count"`
	UpdateSeq          int    `json:"update_seq"`
	PurgeSeq           int    `json:"purge_seq"`
	CompactRunning     bool   `json:"compact_running"`
	DiskSize           int    `json:"disk_size"`
	DataSize           int    `json:"data_size"`
	InstanceStartTime  string `json:"instance_start_time"`
	DiskFormatVersion  int    `json:"disk_format_version"`
	CommittedUpdateSeq int    `json:"committed_update_seq"`
}

type DatabaseResponse struct {
	Ok bool
}

type Error struct {
	Method     string
	Url        string
	StatusCode int
	Type       string `json:"error"`
	Reason     string
}

func (e *Error) Error() string {
	return fmt.Sprintf(
		"CouchDB - %s %s, Status Code: %d, Error: %s, Reason: %s",
		e.Method,
		e.Url,
		e.StatusCode,
		e.Type,
		e.Reason,
	)
}

type CouchDoc interface {
	GetDocument() *Document
}

type Document struct {
	Id          string                `json:"_id,omitempty"`
	Rev         string                `json:"_rev,omitempty"`
	Attachments map[string]Attachment `json:"_attachments,omitempty"`
}

func (d *Document) GetDocument() *Document {
	return d
}

type DesignDocument struct {
	Document
	Language string                        `json:"language,omitempty"`
	Views    map[string]DesignDocumentView `json:"views,omitempty"`
}

func (dd DesignDocument) Name() string {
	return strings.TrimPrefix(dd.Id, "_design/")
}

type DesignDocumentView struct {
	Map    string `json:"map,omitempty"`
	Reduce string `json:"reduce,omitempty"`
}

// http://docs.couchdb.org/en/latest/api/document/common.html#creating-multiple-attachments
type Attachment struct {
	Follows     bool   `json:"follows"`
	ContentType string `json:"content_type"`
	Length      int64  `json:"length"`
}

type DocumentResponse struct {
	Ok  bool
	Id  string
	Rev string
}

// http://docs.couchdb.org/en/latest/api/server/common.html#active-tasks
type Task struct {
	ChangesDone  int `json:"changes_done"`
	Database     string
	Pid          string
	Progress     int
	StartedOn    int `json:"started_on"`
	Status       string
	Task         string
	TotalChanges int `json:"total_changes"`
	Type         string
	UpdatedOn    string `json:"updated_on"`
}

type QueryParameters struct {
	Conflicts       bool   `url:"conflicts"`
	Descending      bool   `url:"descending"`
	EndKey          string `url:"endkey"`
	EndKeyDocId     string `url:"end_key_doc_id"`
	Group           bool   `url:"group"`
	GroupLevel      int    `url:"group_level"`
	IncludeDocs     bool   `url:"include_docs"`
	Attachments     bool   `url:"attachments"`
	AttEncodingInfo bool   `url:"att_encoding_info"`
	InclusiveEnd    bool   `url:"inclusive_end"`
	Key             string `url:"key"`
	Limit           int    `url:"limit"`
	Reduce          bool   `url:"reduce"`
	Skip            int    `url:"skip"`
	Stale           string `url:"stale"`
	StartKey        string `url:"startkey"`
	StartKeyDocId   string `url:"startkey_docid"`
	UpdateSeq       bool   `url:"update_seq"`
}

type ViewResponse struct {
	Offset    int   `json:"offset,omitempty"`
	Rows      []Row `json:"rows,omitempty"`
	TotalRows int   `json:"total_rows,omitempty"`
	UpdateSeq int   `json:"update_seq,omitempty"`
}

type Row struct {
	Id    string                 `json:"id"`
	Key   interface{}            `json:"key"`
	Value interface{}            `json:"value,omitempty"`
	Doc   map[string]interface{} `json:"doc,omitempty"`
}

// http://docs.couchdb.org/en/latest/api/database/bulk-api.html#post--db-_bulk_docs
type BulkDoc struct {
	AllOrNothing bool          `json:"all_or_nothing,omitempty"`
	Docs         []interface{} `json:"docs"`
	NewEdits     bool          `json:"new_edits,omitempty"`
}

// http://docs.couchdb.org/en/latest/api/server/authn.html#cookie-authentication
type Credentials struct {
	Name     string `json:"name"`
	Password string `json:"password"`
}

type PostSessionResponse struct {
	Ok    bool
	Name  string
	Roles []string
}

type User struct {
	Document
	DerivedKey     string   `json:"derived_key,omitempty"`
	Name           string   `json:"name,omitempty"`
	Roles          []string `json:"roles"`
	Password       string   `json:"password,omitempty"`     // plain text password when creating the user
	PasswordSha    string   `json:"password_sha,omitempty"` // hashed password when requesting user information
	PasswordScheme string   `json:"password_scheme,omitempty"`
	Salt           string   `json:"salt,omitempty"`
	Type           string   `json:"type,omitempty"`
	Iterations     int      `json:"iterations,omitempty"`
}

func NewUser(name, password string, roles []string) User {
	user := User{
		Document: Document{
			Id: "org.couchdb.user:" + name,
		},
		DerivedKey:     "",
		Name:           name,
		Roles:          roles,
		Password:       password,
		PasswordSha:    "",
		PasswordScheme: "",
		Salt:           "",
		Type:           "user",
	}
	return user
}

type GetSessionResponse struct {
	Info struct {
		Authenticated          string   `json:"authenticated"`
		AuthenticationDb       string   `json:"authentication_db"`
		AuthenticationHandlers []string `json:"authentication_handlers"`
	} `json:"info"`
	Ok          bool `json:"ok"`
	UserContext struct {
		Db    string   `json:"db"`
		Name  string   `json:"name"`
		Roles []string `json:"roles"`
	} `json:"userCtx"`
}
<|endoftext|>


<commit_before>package gom

import (
	"fmt"
	"reflect"
)

type CreateSql func(TableModel) (string, []interface{})

type BinaryUnmarshaler interface {
	UnmarshalBinary(dbytes []byte) (interface{}, error)
}

type SqlGenerator struct {
	createSql   CreateSql
	tableModels []TableModel
}
type SqlFactory interface {
	Insert(TableModel) (string, []interface{})
	Update(TableModel) (string, []interface{})
	Replace(TableModel) (string, []interface{})
	Delete(TableModel) (string, []interface{})
	Query(TableModel) (string, []interface{})
}
type RowChooser interface {
	Scan(dest ...interface{}) error
}

type TableModel struct {
	ModelType  reflect.Type
	ModelValue reflect.Value
	TableName  string
	Columns    []Column
	Primary    Column
	Cnd        Condition
}
type Column struct {
	ColumnType reflect.Type
	ColumnName string
	FieldName  string
	QueryField string
	IsPrimary  bool
	Auto       bool
}
type Condition interface {
	State() string
	Value() []interface{}
	Or(sql string, values ...interface{}) Condition
	And(sql string, values ...interface{}) Condition
	AndIn(name string, values ...interface{}) Condition
	OrIn(name string, values ...interface{}) Condition
	Raw(sql string, values ...interface{}) Condition
}
type Conditions struct {
	states string
	values []interface{}
}

func Cnd(sql string, values ...interface{}) Condition {
	return &Conditions{sql, values}
}
func (c Conditions) State() string {
	return c.states
}
func (c Conditions) Value() []interface{} {
	return c.values
}
func (c Conditions) And(sql string, values ...interface{}) Condition {
	if c.states != "" {
		c.states += " and "
	}
	c.states += sql
	c.values = append(c.values, values)
	return c
}
func (c Conditions) Or(sql string, values ...interface{}) Condition {
	if c.states != "" {
		c.states += " or "
	}
	c.states += sql
	c.values = append(c.values, values)
	return c
}
func (c Conditions) AndIn(name string, values ...interface{}) Condition {
	if len(values) > 0 {
		if c.states != "" {
			c.states += " and "
		}
		sql, datas := makeInSql(name, values...)
		c.states += sql
		c.values = append(c.values, datas...)
	}
	return c
}

func (c Conditions) OrIn(name string, values ...interface{}) Condition {
	if len(values) > 0 {
		if c.states != "" {
			c.states += " or "
		}
		sql, datas := makeInSql(name, values...)
		c.states += sql
		c.values = append(c.values, datas...)
	}
	return c
}
func (c Conditions) Raw(name string, values ...interface{}) Condition {
	if len(values) > 0 {
		sql, datas := makeInSql(name, values...)
		c.states += sql
		c.values = append(c.values, datas...)
	}
	return c
}

func splitArrays(values interface{}) []interface{} {
	var results []interface{}
	val := reflect.ValueOf(values)
	_, ptr, sli := getType(values)
	if ptr {
		val = val.Elem()
	}
	if sli {
		lens := val.Len()
		if lens > 0 {
			for i := 0; i < lens; i++ {
				datas := splitArrays(val.Index(i).Interface())
				results = append(results, datas...)
			}
		}
	} else {
		results = append(results, val.Interface())
	}
	return results

}

func makeInSql(name string, values ...interface{}) (string, []interface{}) {
	sql := name + " in ("
	var datas []interface{}
	if len(values) == 0 {
		return "", datas
	}
	datas = splitArrays(values)
	for i := 0; i < len(datas); i++ {
		if i == 0 {
			sql += " ? "
		} else {
			sql += ", ? "
		}
	}
	sql += ")"
	if debug {
		fmt.Println("make in sql was :", sql, datas)
	}
	return sql, datas
}

func (mo TableModel) InsertValues() []interface{} {
	var interfaces []interface{}
	results := reflect.Indirect(reflect.ValueOf(&interfaces))
	for _, column := range mo.Columns {
		vars := reflect.ValueOf(mo.ModelValue.FieldByName(column.FieldName).Interface())
		if results.Kind() == reflect.Ptr {
			results.Set(reflect.Append(results, vars.Addr()))
		} else {
			results.Set(reflect.Append(results, vars))
		}
	}
	return interfaces
}
func (m TableModel) GetPrimary() interface{} {
	return m.ModelValue.FieldByName(m.Primary.FieldName).Interface()
}
func (m TableModel) GetPrimaryCondition() Condition {
	if IsEmpty(m.GetPrimary()) || m.Primary.IsPrimary == false {
		return nil
	} else {
		return &Conditions{"`" + m.Primary.ColumnName + "` = ?", []interface{}{m.GetPrimary()}}
	}
}
<commit_msg>修复事务无效的问题<commit_after>package gom

import (
	"fmt"
	"reflect"
)

type CreateSql func(TableModel) (string, []interface{})

type BinaryUnmarshaler interface {
	UnmarshalBinary(dbytes []byte) (interface{}, error)
}

type SqlGenerator struct {
	createSql   CreateSql
	tableModels []TableModel
}
type SqlFactory interface {
	Insert(TableModel) (string, []interface{})
	Update(TableModel) (string, []interface{})
	Replace(TableModel) (string, []interface{})
	Delete(TableModel) (string, []interface{})
	Query(TableModel) (string, []interface{})
}
type RowChooser interface {
	Scan(dest ...interface{}) error
}

type TableModel struct {
	ModelType  reflect.Type
	ModelValue reflect.Value
	TableName  string
	Columns    []Column
	Primary    Column
	Cnd        Condition
}
type Column struct {
	ColumnType reflect.Type
	ColumnName string
	FieldName  string
	QueryField string
	IsPrimary  bool
	Auto       bool
}
type Condition interface {
	State() string
	Value() []interface{}
	Or(sql string, values ...interface{}) Condition
	And(sql string, values ...interface{}) Condition
	AndIn(name string, values ...interface{}) Condition
	OrIn(name string, values ...interface{}) Condition
	Raw(sql string, values ...interface{}) Condition
}
type Conditions struct {
	states string
	values []interface{}
}

func Cnd(sql string, values ...interface{}) Condition {
	return &Conditions{sql, values}
}
func (c Conditions) State() string {
	return c.states
}
func (c Conditions) Value() []interface{} {
	return c.values
}
func (c Conditions) And(sql string, values ...interface{}) Condition {
	if c.states != "" {
		c.states += " and "
	}
	c.states += sql
	c.values = append(c.values, values)
	return c
}
func (c Conditions) Or(sql string, values ...interface{}) Condition {
	if c.states != "" {
		c.states += " or "
	}
	c.states += sql
	c.values = append(c.values, values)
	return c
}
func (c Conditions) AndIn(name string, values ...interface{}) Condition {
	if len(values) > 0 {
		if c.states != "" {
			c.states += " and "
		}
		sql, datas := makeInSql(name, values...)
		c.states += sql
		c.values = append(c.values, datas...)
	}
	return c
}

func (c Conditions) OrIn(name string, values ...interface{}) Condition {
	if len(values) > 0 {
		if c.states != "" {
			c.states += " or "
		}
		sql, datas := makeInSql(name, values...)
		c.states += sql
		c.values = append(c.values, datas...)
	}
	return c
}
func (c Conditions) Raw(sql string, values ...interface{}) Condition {
	c.states += sql
	if len(values) > 0 {
		c.values = append(c.values, values...)
	}
	return c
}

func splitArrays(values interface{}) []interface{} {
	var results []interface{}
	val := reflect.ValueOf(values)
	_, ptr, sli := getType(values)
	if ptr {
		val = val.Elem()
	}
	if sli {
		lens := val.Len()
		if lens > 0 {
			for i := 0; i < lens; i++ {
				datas := splitArrays(val.Index(i).Interface())
				results = append(results, datas...)
			}
		}
	} else {
		results = append(results, val.Interface())
	}
	return results

}

func makeInSql(name string, values ...interface{}) (string, []interface{}) {
	sql := name + " in ("
	var datas []interface{}
	if len(values) == 0 {
		return "", datas
	}
	datas = splitArrays(values)
	for i := 0; i < len(datas); i++ {
		if i == 0 {
			sql += " ? "
		} else {
			sql += ", ? "
		}
	}
	sql += ")"
	if debug {
		fmt.Println("make in sql was :", sql, datas)
	}
	return sql, datas
}

func (mo TableModel) InsertValues() []interface{} {
	var interfaces []interface{}
	results := reflect.Indirect(reflect.ValueOf(&interfaces))
	for _, column := range mo.Columns {
		vars := reflect.ValueOf(mo.ModelValue.FieldByName(column.FieldName).Interface())
		if results.Kind() == reflect.Ptr {
			results.Set(reflect.Append(results, vars.Addr()))
		} else {
			results.Set(reflect.Append(results, vars))
		}
	}
	return interfaces
}
func (m TableModel) GetPrimary() interface{} {
	return m.ModelValue.FieldByName(m.Primary.FieldName).Interface()
}
func (m TableModel) GetPrimaryCondition() Condition {
	if IsEmpty(m.GetPrimary()) || m.Primary.IsPrimary == false {
		return nil
	} else {
		return &Conditions{"`" + m.Primary.ColumnName + "` = ?", []interface{}{m.GetPrimary()}}
	}
}
<|endoftext|>


<commit_before>// Copyright 2018 The Terraformer 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 aws

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/GoogleCloudPlatform/terraformer/terraformutils"

	"github.com/aws/aws-sdk-go-v2/service/iam"

	"github.com/aws/aws-sdk-go-v2/aws"
)

var IamAllowEmptyValues = []string{"tags."}

var IamAdditionalFields = map[string]interface{}{}

type IamGenerator struct {
	AWSService
}

func (g *IamGenerator) InitResources() error {
	config, e := g.generateConfig()
	if e != nil {
		return e
	}
	svc := iam.New(config)
	g.Resources = []terraformutils.Resource{}
	err := g.getUsers(svc)
	if err != nil {
		log.Println(err)
	}

	err = g.getGroups(svc)
	if err != nil {
		log.Println(err)
	}

	err = g.getPolicies(svc)
	if err != nil {
		log.Println(err)
	}

	err = g.getRoles(svc)
	if err != nil {
		log.Println(err)
	}

	err = g.getInstanceProfiles(svc)
	if err != nil {
		log.Println(err)
	}

	return nil
}

func (g *IamGenerator) getRoles(svc *iam.Client) error {
	p := iam.NewListRolesPaginator(svc.ListRolesRequest(&iam.ListRolesInput{}))
	for p.Next(context.Background()) {
		for _, role := range p.CurrentPage().Roles {
			roleName := aws.StringValue(role.RoleName)
			g.Resources = append(g.Resources, terraformutils.NewSimpleResource(
				roleName,
				roleName,
				"aws_iam_role",
				"aws",
				IamAllowEmptyValues))
			rolePoliciesPage := iam.NewListRolePoliciesPaginator(svc.ListRolePoliciesRequest(&iam.ListRolePoliciesInput{RoleName: role.RoleName}))
			for rolePoliciesPage.Next(context.Background()) {
				for _, policyName := range rolePoliciesPage.CurrentPage().PolicyNames {
					g.Resources = append(g.Resources, terraformutils.NewSimpleResource(
						roleName+":"+policyName,
						roleName+"_"+policyName,
						"aws_iam_role_policy",
						"aws",
						IamAllowEmptyValues))
				}
			}
			if err := rolePoliciesPage.Err(); err != nil {
				log.Println(err)
				continue
			}
			roleAttachedPoliciesPage := iam.NewListAttachedRolePoliciesPaginator(svc.ListAttachedRolePoliciesRequest(&iam.ListAttachedRolePoliciesInput{
				RoleName: &roleName,
			}))
			for roleAttachedPoliciesPage.Next(context.Background()) {
				for _, attachedPolicy := range roleAttachedPoliciesPage.CurrentPage().AttachedPolicies {
					g.Resources = append(g.Resources, terraformutils.NewResource(
						roleName+"/"+*attachedPolicy.PolicyArn,
						roleName+"_"+*attachedPolicy.PolicyName,
						"aws_iam_role_policy_attachment",
						"aws",
						map[string]string{
							"name":       roleName,
							"policy_arn": *attachedPolicy.PolicyArn,
						},
						IamAllowEmptyValues,
						map[string]interface{}{}))
				}
			}
			if err := roleAttachedPoliciesPage.Err(); err != nil {
				log.Println(err)
				continue
			}
		}
	}
	return p.Err()
}

func (g *IamGenerator) getUsers(svc *iam.Client) error {
	p := iam.NewListUsersPaginator(svc.ListUsersRequest(&iam.ListUsersInput{}))
	for p.Next(context.Background()) {
		for _, user := range p.CurrentPage().Users {
			resourceName := aws.StringValue(user.UserName)
			g.Resources = append(g.Resources, terraformutils.NewResource(
				resourceName,
				aws.StringValue(user.UserId),
				"aws_iam_user",
				"aws",
				map[string]string{
					"force_destroy": "false",
				},
				IamAllowEmptyValues,
				map[string]interface{}{}))
			err := g.getUserPolices(svc, user.UserName)
			if err != nil {
				log.Println(err)
			}
			err = g.getUserPolicyAttachment(svc, user.UserName)
			if err != nil {
				log.Println(err)
			}
			err = g.getUserGroup(svc, user.UserName)
			if err != nil {
				log.Println(err)
			}
		}
	}
	return p.Err()
}

func (g *IamGenerator) getUserGroup(svc *iam.Client, userName *string) error {
	p := iam.NewListGroupsForUserPaginator(svc.ListGroupsForUserRequest(&iam.ListGroupsForUserInput{UserName: userName}))
	for p.Next(context.Background()) {
		for _, group := range p.CurrentPage().Groups {
			userGroupMembership := *userName + "/" + *group.GroupName
			g.Resources = append(g.Resources, terraformutils.NewResource(
				userGroupMembership,
				userGroupMembership,
				"aws_iam_user_group_membership",
				"aws",
				map[string]string{
					"user":     *userName,
					"groups.#": "1",
					"groups.0": *group.GroupName,
				},
				IamAllowEmptyValues,
				IamAdditionalFields,
			))
		}
	}
	return p.Err()
}

func (g *IamGenerator) getUserPolices(svc *iam.Client, userName *string) error {
	p := iam.NewListUserPoliciesPaginator(svc.ListUserPoliciesRequest(&iam.ListUserPoliciesInput{UserName: userName}))
	for p.Next(context.Background()) {
		for _, policy := range p.CurrentPage().PolicyNames {
			resourceName := aws.StringValue(userName) + "_" + policy
			resourceName = strings.Replace(resourceName, "@", "", -1)
			policyID := aws.StringValue(userName) + ":" + policy
			g.Resources = append(g.Resources, terraformutils.NewSimpleResource(
				policyID,
				resourceName,
				"aws_iam_user_policy",
				"aws",
				IamAllowEmptyValues))
		}
	}
	return p.Err()
}

func (g *IamGenerator) getUserPolicyAttachment(svc *iam.Client, userName *string) error {
	p := iam.NewListAttachedUserPoliciesPaginator(svc.ListAttachedUserPoliciesRequest(&iam.ListAttachedUserPoliciesInput{
		UserName: userName,
	}))
	for p.Next(context.Background()) {
		for _, attachedPolicy := range p.CurrentPage().AttachedPolicies {
			g.Resources = append(g.Resources, terraformutils.NewResource(
				*userName+"/"+*attachedPolicy.PolicyArn,
				*userName+"_"+*attachedPolicy.PolicyName,
				"aws_iam_user_policy_attachment",
				"aws",
				map[string]string{
					"user":       *userName,
					"policy_arn": *attachedPolicy.PolicyArn,
				},
				IamAllowEmptyValues,
				map[string]interface{}{}))
		}
	}
	return p.Err()
}

func (g *IamGenerator) getPolicies(svc *iam.Client) error {
	p := iam.NewListPoliciesPaginator(svc.ListPoliciesRequest(&iam.ListPoliciesInput{Scope: iam.PolicyScopeTypeLocal}))
	for p.Next(context.Background()) {
		for _, policy := range p.CurrentPage().Policies {
			resourceName := aws.StringValue(policy.PolicyName)
			policyARN := aws.StringValue(policy.Arn)

			g.Resources = append(g.Resources, terraformutils.NewSimpleResource(
				policyARN,
				resourceName,
				"aws_iam_policy",
				"aws",
				IamAllowEmptyValues))
		}
	}
	return p.Err()
}

func (g *IamGenerator) getGroups(svc *iam.Client) error {
	p := iam.NewListGroupsPaginator(svc.ListGroupsRequest(&iam.ListGroupsInput{}))
	for p.Next(context.Background()) {
		for _, group := range p.CurrentPage().Groups {
			resourceName := aws.StringValue(group.GroupName)
			g.Resources = append(g.Resources, terraformutils.NewSimpleResource(
				resourceName,
				resourceName,
				"aws_iam_group",
				"aws",
				IamAllowEmptyValues))
			g.getGroupPolicies(svc, group)
			g.getAttachedGroupPolicies(svc, group)
		}
	}
	return p.Err()
}

func (g *IamGenerator) getGroupPolicies(svc *iam.Client, group iam.Group) {
	groupPoliciesPage := iam.NewListGroupPoliciesPaginator(svc.ListGroupPoliciesRequest(&iam.ListGroupPoliciesInput{GroupName: group.GroupName}))
	for groupPoliciesPage.Next(context.Background()) {
		for _, policy := range groupPoliciesPage.CurrentPage().PolicyNames {
			id := *group.GroupName + ":" + policy
			groupPolicyName := *group.GroupName + "_" + policy
			g.Resources = append(g.Resources, terraformutils.NewResource(
				id,
				groupPolicyName,
				"aws_iam_group_policy",
				"aws",
				map[string]string{},
				IamAllowEmptyValues,
				IamAdditionalFields))
		}
	}
	if err := groupPoliciesPage.Err(); err != nil {
		log.Println(err)
	}
}

func (g *IamGenerator) getAttachedGroupPolicies(svc *iam.Client, group iam.Group) {
	groupAttachedPoliciesPage := iam.NewListAttachedGroupPoliciesPaginator(svc.ListAttachedGroupPoliciesRequest(
		&iam.ListAttachedGroupPoliciesInput{GroupName: group.GroupName}))
	for groupAttachedPoliciesPage.Next(context.Background()) {
		for _, attachedPolicy := range groupAttachedPoliciesPage.CurrentPage().AttachedPolicies {
			if !strings.Contains(*attachedPolicy.PolicyArn, "arn:aws:iam::aws") {
				continue // map only AWS managed policies since others should be managed by
			}
			id := *group.GroupName + "/" + *attachedPolicy.PolicyArn
			g.Resources = append(g.Resources, terraformutils.NewResource(
				id,
				*group.GroupName+"_"+*attachedPolicy.PolicyName,
				"aws_iam_group_policy_attachment",
				"aws",
				map[string]string{
					"group":      *group.GroupName,
					"policy_arn": *attachedPolicy.PolicyArn,
				},
				IamAllowEmptyValues,
				IamAdditionalFields))
		}
	}
	if err := groupAttachedPoliciesPage.Err(); err != nil {
		log.Println(err)
	}
}

func (g *IamGenerator) getInstanceProfiles(svc *iam.Client) error {
	p := iam.NewListInstanceProfilesPaginator(svc.ListInstanceProfilesRequest(&iam.ListInstanceProfilesInput{}))
	for p.Next(context.Background()) {
		for _, instanceProfile := range p.CurrentPage().InstanceProfiles {
			resourceName := *instanceProfile.InstanceProfileName

			g.Resources = append(g.Resources, terraformutils.NewResource(
				resourceName,
				resourceName,
				"aws_iam_instance_profile",
				"aws",
				map[string]string{
					"name": resourceName,
				},
				IamAllowEmptyValues,
				IamAdditionalFields))
		}
	}
	return p.Err()
}

// PostGenerateHook for add policy json as heredoc
func (g *IamGenerator) PostConvertHook() error {
	for i, resource := range g.Resources {
		switch {
		case resource.InstanceInfo.Type == "aws_iam_policy" ||
			resource.InstanceInfo.Type == "aws_iam_user_policy" ||
			resource.InstanceInfo.Type == "aws_iam_group_policy" ||
			resource.InstanceInfo.Type == "aws_iam_role_policy":
			policy := g.escapeAwsInterpolation(resource.Item["policy"].(string))
			resource.Item["policy"] = fmt.Sprintf(`<<POLICY
%s
POLICY`, policy)
		case resource.InstanceInfo.Type == "aws_iam_role":
			policy := g.escapeAwsInterpolation(resource.Item["assume_role_policy"].(string))
			g.Resources[i].Item["assume_role_policy"] = fmt.Sprintf(`<<POLICY
%s
POLICY`, policy)
		case resource.InstanceInfo.Type == "aws_iam_instance_profile":
			delete(resource.Item, "roles")
		}
	}
	return nil
}
<commit_msg>aws_iam_role_policy_attachment fix<commit_after>// Copyright 2018 The Terraformer 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 aws

import (
	"context"
	"fmt"
	"log"
	"strings"

	"github.com/GoogleCloudPlatform/terraformer/terraformutils"

	"github.com/aws/aws-sdk-go-v2/service/iam"

	"github.com/aws/aws-sdk-go-v2/aws"
)

var IamAllowEmptyValues = []string{"tags."}

var IamAdditionalFields = map[string]interface{}{}

type IamGenerator struct {
	AWSService
}

func (g *IamGenerator) InitResources() error {
	config, e := g.generateConfig()
	if e != nil {
		return e
	}
	svc := iam.New(config)
	g.Resources = []terraformutils.Resource{}
	err := g.getUsers(svc)
	if err != nil {
		log.Println(err)
	}

	err = g.getGroups(svc)
	if err != nil {
		log.Println(err)
	}

	err = g.getPolicies(svc)
	if err != nil {
		log.Println(err)
	}

	err = g.getRoles(svc)
	if err != nil {
		log.Println(err)
	}

	err = g.getInstanceProfiles(svc)
	if err != nil {
		log.Println(err)
	}

	return nil
}

func (g *IamGenerator) getRoles(svc *iam.Client) error {
	p := iam.NewListRolesPaginator(svc.ListRolesRequest(&iam.ListRolesInput{}))
	for p.Next(context.Background()) {
		for _, role := range p.CurrentPage().Roles {
			roleName := aws.StringValue(role.RoleName)
			g.Resources = append(g.Resources, terraformutils.NewSimpleResource(
				roleName,
				roleName,
				"aws_iam_role",
				"aws",
				IamAllowEmptyValues))
			rolePoliciesPage := iam.NewListRolePoliciesPaginator(svc.ListRolePoliciesRequest(&iam.ListRolePoliciesInput{RoleName: role.RoleName}))
			for rolePoliciesPage.Next(context.Background()) {
				for _, policyName := range rolePoliciesPage.CurrentPage().PolicyNames {
					g.Resources = append(g.Resources, terraformutils.NewSimpleResource(
						roleName+":"+policyName,
						roleName+"_"+policyName,
						"aws_iam_role_policy",
						"aws",
						IamAllowEmptyValues))
				}
			}
			if err := rolePoliciesPage.Err(); err != nil {
				log.Println(err)
				continue
			}
			roleAttachedPoliciesPage := iam.NewListAttachedRolePoliciesPaginator(svc.ListAttachedRolePoliciesRequest(&iam.ListAttachedRolePoliciesInput{
				RoleName: &roleName,
			}))
			for roleAttachedPoliciesPage.Next(context.Background()) {
				for _, attachedPolicy := range roleAttachedPoliciesPage.CurrentPage().AttachedPolicies {
					g.Resources = append(g.Resources, terraformutils.NewResource(
						roleName+"/"+*attachedPolicy.PolicyArn,
						roleName+"_"+*attachedPolicy.PolicyName,
						"aws_iam_role_policy_attachment",
						"aws",
						map[string]string{
							"role":       roleName,
							"policy_arn": *attachedPolicy.PolicyArn,
						},
						IamAllowEmptyValues,
						map[string]interface{}{}))
				}
			}
			if err := roleAttachedPoliciesPage.Err(); err != nil {
				log.Println(err)
				continue
			}
		}
	}
	return p.Err()
}

func (g *IamGenerator) getUsers(svc *iam.Client) error {
	p := iam.NewListUsersPaginator(svc.ListUsersRequest(&iam.ListUsersInput{}))
	for p.Next(context.Background()) {
		for _, user := range p.CurrentPage().Users {
			resourceName := aws.StringValue(user.UserName)
			g.Resources = append(g.Resources, terraformutils.NewResource(
				resourceName,
				aws.StringValue(user.UserId),
				"aws_iam_user",
				"aws",
				map[string]string{
					"force_destroy": "false",
				},
				IamAllowEmptyValues,
				map[string]interface{}{}))
			err := g.getUserPolices(svc, user.UserName)
			if err != nil {
				log.Println(err)
			}
			err = g.getUserPolicyAttachment(svc, user.UserName)
			if err != nil {
				log.Println(err)
			}
			err = g.getUserGroup(svc, user.UserName)
			if err != nil {
				log.Println(err)
			}
		}
	}
	return p.Err()
}

func (g *IamGenerator) getUserGroup(svc *iam.Client, userName *string) error {
	p := iam.NewListGroupsForUserPaginator(svc.ListGroupsForUserRequest(&iam.ListGroupsForUserInput{UserName: userName}))
	for p.Next(context.Background()) {
		for _, group := range p.CurrentPage().Groups {
			userGroupMembership := *userName + "/" + *group.GroupName
			g.Resources = append(g.Resources, terraformutils.NewResource(
				userGroupMembership,
				userGroupMembership,
				"aws_iam_user_group_membership",
				"aws",
				map[string]string{
					"user":     *userName,
					"groups.#": "1",
					"groups.0": *group.GroupName,
				},
				IamAllowEmptyValues,
				IamAdditionalFields,
			))
		}
	}
	return p.Err()
}

func (g *IamGenerator) getUserPolices(svc *iam.Client, userName *string) error {
	p := iam.NewListUserPoliciesPaginator(svc.ListUserPoliciesRequest(&iam.ListUserPoliciesInput{UserName: userName}))
	for p.Next(context.Background()) {
		for _, policy := range p.CurrentPage().PolicyNames {
			resourceName := aws.StringValue(userName) + "_" + policy
			resourceName = strings.Replace(resourceName, "@", "", -1)
			policyID := aws.StringValue(userName) + ":" + policy
			g.Resources = append(g.Resources, terraformutils.NewSimpleResource(
				policyID,
				resourceName,
				"aws_iam_user_policy",
				"aws",
				IamAllowEmptyValues))
		}
	}
	return p.Err()
}

func (g *IamGenerator) getUserPolicyAttachment(svc *iam.Client, userName *string) error {
	p := iam.NewListAttachedUserPoliciesPaginator(svc.ListAttachedUserPoliciesRequest(&iam.ListAttachedUserPoliciesInput{
		UserName: userName,
	}))
	for p.Next(context.Background()) {
		for _, attachedPolicy := range p.CurrentPage().AttachedPolicies {
			g.Resources = append(g.Resources, terraformutils.NewResource(
				*userName+"/"+*attachedPolicy.PolicyArn,
				*userName+"_"+*attachedPolicy.PolicyName,
				"aws_iam_user_policy_attachment",
				"aws",
				map[string]string{
					"user":       *userName,
					"policy_arn": *attachedPolicy.PolicyArn,
				},
				IamAllowEmptyValues,
				map[string]interface{}{}))
		}
	}
	return p.Err()
}

func (g *IamGenerator) getPolicies(svc *iam.Client) error {
	p := iam.NewListPoliciesPaginator(svc.ListPoliciesRequest(&iam.ListPoliciesInput{Scope: iam.PolicyScopeTypeLocal}))
	for p.Next(context.Background()) {
		for _, policy := range p.CurrentPage().Policies {
			resourceName := aws.StringValue(policy.PolicyName)
			policyARN := aws.StringValue(policy.Arn)

			g.Resources = append(g.Resources, terraformutils.NewSimpleResource(
				policyARN,
				resourceName,
				"aws_iam_policy",
				"aws",
				IamAllowEmptyValues))
		}
	}
	return p.Err()
}

func (g *IamGenerator) getGroups(svc *iam.Client) error {
	p := iam.NewListGroupsPaginator(svc.ListGroupsRequest(&iam.ListGroupsInput{}))
	for p.Next(context.Background()) {
		for _, group := range p.CurrentPage().Groups {
			resourceName := aws.StringValue(group.GroupName)
			g.Resources = append(g.Resources, terraformutils.NewSimpleResource(
				resourceName,
				resourceName,
				"aws_iam_group",
				"aws",
				IamAllowEmptyValues))
			g.getGroupPolicies(svc, group)
			g.getAttachedGroupPolicies(svc, group)
		}
	}
	return p.Err()
}

func (g *IamGenerator) getGroupPolicies(svc *iam.Client, group iam.Group) {
	groupPoliciesPage := iam.NewListGroupPoliciesPaginator(svc.ListGroupPoliciesRequest(&iam.ListGroupPoliciesInput{GroupName: group.GroupName}))
	for groupPoliciesPage.Next(context.Background()) {
		for _, policy := range groupPoliciesPage.CurrentPage().PolicyNames {
			id := *group.GroupName + ":" + policy
			groupPolicyName := *group.GroupName + "_" + policy
			g.Resources = append(g.Resources, terraformutils.NewResource(
				id,
				groupPolicyName,
				"aws_iam_group_policy",
				"aws",
				map[string]string{},
				IamAllowEmptyValues,
				IamAdditionalFields))
		}
	}
	if err := groupPoliciesPage.Err(); err != nil {
		log.Println(err)
	}
}

func (g *IamGenerator) getAttachedGroupPolicies(svc *iam.Client, group iam.Group) {
	groupAttachedPoliciesPage := iam.NewListAttachedGroupPoliciesPaginator(svc.ListAttachedGroupPoliciesRequest(
		&iam.ListAttachedGroupPoliciesInput{GroupName: group.GroupName}))
	for groupAttachedPoliciesPage.Next(context.Background()) {
		for _, attachedPolicy := range groupAttachedPoliciesPage.CurrentPage().AttachedPolicies {
			if !strings.Contains(*attachedPolicy.PolicyArn, "arn:aws:iam::aws") {
				continue // map only AWS managed policies since others should be managed by
			}
			id := *group.GroupName + "/" + *attachedPolicy.PolicyArn
			g.Resources = append(g.Resources, terraformutils.NewResource(
				id,
				*group.GroupName+"_"+*attachedPolicy.PolicyName,
				"aws_iam_group_policy_attachment",
				"aws",
				map[string]string{
					"group":      *group.GroupName,
					"policy_arn": *attachedPolicy.PolicyArn,
				},
				IamAllowEmptyValues,
				IamAdditionalFields))
		}
	}
	if err := groupAttachedPoliciesPage.Err(); err != nil {
		log.Println(err)
	}
}

func (g *IamGenerator) getInstanceProfiles(svc *iam.Client) error {
	p := iam.NewListInstanceProfilesPaginator(svc.ListInstanceProfilesRequest(&iam.ListInstanceProfilesInput{}))
	for p.Next(context.Background()) {
		for _, instanceProfile := range p.CurrentPage().InstanceProfiles {
			resourceName := *instanceProfile.InstanceProfileName

			g.Resources = append(g.Resources, terraformutils.NewResource(
				resourceName,
				resourceName,
				"aws_iam_instance_profile",
				"aws",
				map[string]string{
					"name": resourceName,
				},
				IamAllowEmptyValues,
				IamAdditionalFields))
		}
	}
	return p.Err()
}

// PostGenerateHook for add policy json as heredoc
func (g *IamGenerator) PostConvertHook() error {
	for i, resource := range g.Resources {
		switch {
		case resource.InstanceInfo.Type == "aws_iam_policy" ||
			resource.InstanceInfo.Type == "aws_iam_user_policy" ||
			resource.InstanceInfo.Type == "aws_iam_group_policy" ||
			resource.InstanceInfo.Type == "aws_iam_role_policy":
			policy := g.escapeAwsInterpolation(resource.Item["policy"].(string))
			resource.Item["policy"] = fmt.Sprintf(`<<POLICY
%s
POLICY`, policy)
		case resource.InstanceInfo.Type == "aws_iam_role":
			policy := g.escapeAwsInterpolation(resource.Item["assume_role_policy"].(string))
			g.Resources[i].Item["assume_role_policy"] = fmt.Sprintf(`<<POLICY
%s
POLICY`, policy)
		case resource.InstanceInfo.Type == "aws_iam_instance_profile":
			delete(resource.Item, "roles")
		}
	}
	return nil
}
<|endoftext|>


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

// Package guestpolicies GuestPolicy osconfig agent tests.
package guestpolicies

import (
	"context"
	"fmt"
	"log"
	"path"
	"regexp"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils/junitxml"
	"github.com/GoogleCloudPlatform/compute-image-tools/osconfig_tests/compute"
	"github.com/GoogleCloudPlatform/compute-image-tools/osconfig_tests/config"
	gcpclients "github.com/GoogleCloudPlatform/compute-image-tools/osconfig_tests/gcp_clients"
	testconfig "github.com/GoogleCloudPlatform/compute-image-tools/osconfig_tests/test_config"
	"github.com/GoogleCloudPlatform/compute-image-tools/osconfig_tests/utils"
	"github.com/kylelemons/godebug/pretty"
	computeApi "google.golang.org/api/compute/v1"

	osconfigpb "github.com/GoogleCloudPlatform/osconfig/_internal/gapi-cloud-osconfig-go/google.golang.org/genproto/googleapis/cloud/osconfig/v1alpha2"
)

var (
	testSuiteName = "OSPackage"
)

var (
	dump = &pretty.Config{IncludeUnexported: true}
)

const (
	packageInstallFunction            = "pkginstall"
	packageRemovalFunction            = "pkgremoval"
	packageInstallFromNewRepoFunction = "pkgfromnewrepo"
	packageUpdateFunction             = "pkgupdate"
	packageNoUpdateFunction           = "pkgnoupdate"
)

type guestPolicyTestSetup struct {
	image         string
	guestPolicyID string
	instanceName  string
	testName      string
	guestPolicy   *osconfigpb.GuestPolicy
	startup       *computeApi.MetadataItems
	machineType   string
	queryPath     string
	assertTimeout time.Duration
}

func newGuestPolicyTestSetup(image, instanceName, testName, queryPath, machineType string, gp *osconfigpb.GuestPolicy, startup *computeApi.MetadataItems, assertTimeout time.Duration) *guestPolicyTestSetup {
	return &guestPolicyTestSetup{
		image:         image,
		guestPolicyID: instanceName,
		instanceName:  instanceName,
		guestPolicy:   gp,
		testName:      testName,
		machineType:   machineType,
		queryPath:     queryPath,
		assertTimeout: assertTimeout,
		startup:       startup,
	}
}

// TestSuite is a OSPackage test suite.
func TestSuite(ctx context.Context, tswg *sync.WaitGroup, testSuites chan *junitxml.TestSuite, logger *log.Logger, testSuiteRegex, testCaseRegex *regexp.Regexp, testProjectConfig *testconfig.Project) {
	defer tswg.Done()

	if testSuiteRegex != nil && !testSuiteRegex.MatchString(testSuiteName) {
		return
	}

	testSuite := junitxml.NewTestSuite(testSuiteName)
	defer testSuite.Finish(testSuites)

	logger.Printf("Running TestSuite %q", testSuite.Name)
	testSetup := generateAllTestSetup(testProjectConfig)
	var wg sync.WaitGroup
	tests := make(chan *junitxml.TestCase)
	for _, setup := range testSetup {
		wg.Add(1)
		go packageManagementTestCase(ctx, setup, tests, &wg, logger, testCaseRegex, testProjectConfig)
	}

	go func() {
		wg.Wait()
		close(tests)
	}()

	for ret := range tests {
		testSuite.TestCase = append(testSuite.TestCase, ret)
	}

	logger.Printf("Finished TestSuite %q", testSuite.Name)
}

func runTest(ctx context.Context, testCase *junitxml.TestCase, testSetup *guestPolicyTestSetup, logger *log.Logger, logwg *sync.WaitGroup, testProjectConfig *testconfig.Project) {
	parent := fmt.Sprintf("projects/%s", testProjectConfig.TestProjectID)

	client, err := gcpclients.GetOsConfigClientV1alpha2()
	if err != nil {
		testCase.WriteFailure("Error getting osconfig client: %v", err)
		return
	}

	req := &osconfigpb.CreateGuestPolicyRequest{
		Parent:        parent,
		GuestPolicyId: testSetup.guestPolicyID,
		GuestPolicy:   testSetup.guestPolicy,
	}

	res, err := client.CreateGuestPolicy(ctx, req)
	if err != nil {
		testCase.WriteFailure("Error running CreateGuestPolicy: %s", utils.GetStatusFromError(err))
		return
	}
	defer cleanupGuestPolicy(ctx, testCase, res)

	computeClient, err := gcpclients.GetComputeClient()
	if err != nil {
		testCase.WriteFailure("Error getting compute client: %v", err)
		return
	}

	testCase.Logf("Creating instance with image %q", testSetup.image)
	var metadataItems []*computeApi.MetadataItems
	metadataItems = append(metadataItems, testSetup.startup)
	metadataItems = append(metadataItems, compute.BuildInstanceMetadataItem("os-config-enabled-prerelease-features", "ospackage"))
	inst, err := utils.CreateComputeInstance(metadataItems, computeClient, testSetup.machineType, testSetup.image, testSetup.instanceName, testProjectConfig.TestProjectID, testProjectConfig.GetZone(), testProjectConfig.ServiceAccountEmail, testProjectConfig.ServiceAccountScopes)
	if err != nil {
		testCase.WriteFailure("Error creating instance: %s", utils.GetStatusFromError(err))
		return
	}
	defer inst.Cleanup()

	storageClient, err := gcpclients.GetStorageClient()
	if err != nil {
		testCase.WriteFailure("Error getting storage client: %v", err)
	}
	logwg.Add(1)
	go inst.StreamSerialOutput(ctx, storageClient, path.Join(testSuiteName, config.LogsPath()), config.LogBucket(), logwg, 1, config.LogPushInterval())

	testCase.Logf("Waiting for agent install to complete")
	if _, err := inst.WaitForGuestAttributes("osconfig_tests/install_done", 5*time.Second, 5*time.Minute); err != nil {
		testCase.WriteFailure("Error waiting for osconfig agent install: %v", err)
		return
	}

	if _, err := inst.WaitForGuestAttributes(testSetup.queryPath, 10*time.Second, testSetup.assertTimeout); err != nil {
		testCase.WriteFailure("error while asserting: %v", err)
		return
	}
}

func packageManagementTestCase(ctx context.Context, testSetup *guestPolicyTestSetup, tests chan *junitxml.TestCase, wg *sync.WaitGroup, logger *log.Logger, regex *regexp.Regexp, testProjectConfig *testconfig.Project) {
	defer wg.Done()

	var logwg sync.WaitGroup

	tc, err := getTestCaseFromTestSetUp(testSetup)
	if err != nil {
		logger.Fatalf("invalid testcase: %+v", err)
		return
	}
	if tc.FilterTestCase(regex) {
		tc.Finish(tests)
	} else {
		logger.Printf("Running TestCase %q", tc.Name)
		runTest(ctx, tc, testSetup, logger, &logwg, testProjectConfig)
		tc.Finish(tests)
		logger.Printf("TestCase %q finished in %fs", tc.Name, tc.Time)
	}

	logwg.Wait()
}

// factory method to get testcase from the testsetup
func getTestCaseFromTestSetUp(testSetup *guestPolicyTestSetup) (*junitxml.TestCase, error) {
	var tc *junitxml.TestCase

	switch testSetup.testName {
	case packageInstallFunction:
		tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package installation] [%s]", path.Base(testSetup.image)))
	case packageRemovalFunction:
		tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package removal] [%s]", path.Base(testSetup.image)))
	case packageInstallFromNewRepoFunction:
		tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Add a new package from new repository] [%s]", path.Base(testSetup.image)))
	case packageUpdateFunction:
		tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package update] [%s]", path.Base(testSetup.image)))
	case packageNoUpdateFunction:
		tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package install doesn't update] [%s]", path.Base(testSetup.image)))
	default:
		return nil, fmt.Errorf("unknown test function name: %s", testSetup.testName)
	}

	return tc, nil
}

func cleanupGuestPolicy(ctx context.Context, testCase *junitxml.TestCase, gp *osconfigpb.GuestPolicy) {
	client, err := gcpclients.GetOsConfigClientV1alpha2()
	if err != nil {
		testCase.WriteFailure(fmt.Sprintf("Error while deleting osconfig: %s", utils.GetStatusFromError(err)))
	}

	if err := client.DeleteGuestPolicy(ctx, &osconfigpb.DeleteGuestPolicyRequest{Name: gp.GetName()}); err != nil {
		testCase.WriteFailure(fmt.Sprintf("Error while deleting osconfig: %s", utils.GetStatusFromError(err)))
	}
}
<commit_msg>Only want to create one GuestPolicy at a time to limit QPS. (#875)<commit_after>//  Copyright 2019 Google Inc. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

// Package guestpolicies GuestPolicy osconfig agent tests.
package guestpolicies

import (
	"context"
	"fmt"
	"log"
	"path"
	"regexp"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/compute-image-tools/go/e2e_test_utils/junitxml"
	"github.com/GoogleCloudPlatform/compute-image-tools/osconfig_tests/compute"
	"github.com/GoogleCloudPlatform/compute-image-tools/osconfig_tests/config"
	gcpclients "github.com/GoogleCloudPlatform/compute-image-tools/osconfig_tests/gcp_clients"
	testconfig "github.com/GoogleCloudPlatform/compute-image-tools/osconfig_tests/test_config"
	"github.com/GoogleCloudPlatform/compute-image-tools/osconfig_tests/utils"
	osconfigV1alpha2 "github.com/GoogleCloudPlatform/osconfig/_internal/gapi-cloud-osconfig-go/cloud.google.com/go/osconfig/apiv1alpha2"
	"github.com/kylelemons/godebug/pretty"
	computeApi "google.golang.org/api/compute/v1"

	osconfigpb "github.com/GoogleCloudPlatform/osconfig/_internal/gapi-cloud-osconfig-go/google.golang.org/genproto/googleapis/cloud/osconfig/v1alpha2"
)

var (
	testSuiteName = "OSPackage"
)

var (
	dump = &pretty.Config{IncludeUnexported: true}
)

const (
	packageInstallFunction            = "pkginstall"
	packageRemovalFunction            = "pkgremoval"
	packageInstallFromNewRepoFunction = "pkgfromnewrepo"
	packageUpdateFunction             = "pkgupdate"
	packageNoUpdateFunction           = "pkgnoupdate"
)

type guestPolicyTestSetup struct {
	image         string
	guestPolicyID string
	instanceName  string
	testName      string
	guestPolicy   *osconfigpb.GuestPolicy
	startup       *computeApi.MetadataItems
	machineType   string
	queryPath     string
	assertTimeout time.Duration
}

func newGuestPolicyTestSetup(image, instanceName, testName, queryPath, machineType string, gp *osconfigpb.GuestPolicy, startup *computeApi.MetadataItems, assertTimeout time.Duration) *guestPolicyTestSetup {
	return &guestPolicyTestSetup{
		image:         image,
		guestPolicyID: instanceName,
		instanceName:  instanceName,
		guestPolicy:   gp,
		testName:      testName,
		machineType:   machineType,
		queryPath:     queryPath,
		assertTimeout: assertTimeout,
		startup:       startup,
	}
}

// TestSuite is a OSPackage test suite.
func TestSuite(ctx context.Context, tswg *sync.WaitGroup, testSuites chan *junitxml.TestSuite, logger *log.Logger, testSuiteRegex, testCaseRegex *regexp.Regexp, testProjectConfig *testconfig.Project) {
	defer tswg.Done()

	if testSuiteRegex != nil && !testSuiteRegex.MatchString(testSuiteName) {
		return
	}

	testSuite := junitxml.NewTestSuite(testSuiteName)
	defer testSuite.Finish(testSuites)

	logger.Printf("Running TestSuite %q", testSuite.Name)
	testSetup := generateAllTestSetup(testProjectConfig)
	var wg sync.WaitGroup
	tests := make(chan *junitxml.TestCase)
	for _, setup := range testSetup {
		wg.Add(1)
		go packageManagementTestCase(ctx, setup, tests, &wg, logger, testCaseRegex, testProjectConfig)
	}

	go func() {
		wg.Wait()
		close(tests)
	}()

	for ret := range tests {
		testSuite.TestCase = append(testSuite.TestCase, ret)
	}

	logger.Printf("Finished TestSuite %q", testSuite.Name)
}

// We only want to create one GuestPolicy at a time to limit QPS.
var gpMx sync.Mutex

func createGuestPolicy(ctx context.Context, client *osconfigV1alpha2.Client, req *osconfigpb.CreateGuestPolicyRequest) (*osconfigpb.GuestPolicy, error) {
	gpMx.Lock()
	defer gpMx.Unlock()
	return client.CreateGuestPolicy(ctx, req)
}

func runTest(ctx context.Context, testCase *junitxml.TestCase, testSetup *guestPolicyTestSetup, logger *log.Logger, logwg *sync.WaitGroup, testProjectConfig *testconfig.Project) {
	parent := fmt.Sprintf("projects/%s", testProjectConfig.TestProjectID)

	client, err := gcpclients.GetOsConfigClientV1alpha2()
	if err != nil {
		testCase.WriteFailure("Error getting osconfig client: %v", err)
		return
	}

	req := &osconfigpb.CreateGuestPolicyRequest{
		Parent:        parent,
		GuestPolicyId: testSetup.guestPolicyID,
		GuestPolicy:   testSetup.guestPolicy,
	}

	res, err := createGuestPolicy(ctx, client, req)
	if err != nil {
		testCase.WriteFailure("Error running CreateGuestPolicy: %s", utils.GetStatusFromError(err))
		return
	}
	defer cleanupGuestPolicy(ctx, testCase, res)

	computeClient, err := gcpclients.GetComputeClient()
	if err != nil {
		testCase.WriteFailure("Error getting compute client: %v", err)
		return
	}

	testCase.Logf("Creating instance with image %q", testSetup.image)
	var metadataItems []*computeApi.MetadataItems
	metadataItems = append(metadataItems, testSetup.startup)
	metadataItems = append(metadataItems, compute.BuildInstanceMetadataItem("os-config-enabled-prerelease-features", "ospackage"))
	inst, err := utils.CreateComputeInstance(metadataItems, computeClient, testSetup.machineType, testSetup.image, testSetup.instanceName, testProjectConfig.TestProjectID, testProjectConfig.GetZone(), testProjectConfig.ServiceAccountEmail, testProjectConfig.ServiceAccountScopes)
	if err != nil {
		testCase.WriteFailure("Error creating instance: %s", utils.GetStatusFromError(err))
		return
	}
	defer inst.Cleanup()

	storageClient, err := gcpclients.GetStorageClient()
	if err != nil {
		testCase.WriteFailure("Error getting storage client: %v", err)
	}
	logwg.Add(1)
	go inst.StreamSerialOutput(ctx, storageClient, path.Join(testSuiteName, config.LogsPath()), config.LogBucket(), logwg, 1, config.LogPushInterval())

	testCase.Logf("Waiting for agent install to complete")
	if _, err := inst.WaitForGuestAttributes("osconfig_tests/install_done", 5*time.Second, 5*time.Minute); err != nil {
		testCase.WriteFailure("Error waiting for osconfig agent install: %v", err)
		return
	}

	if _, err := inst.WaitForGuestAttributes(testSetup.queryPath, 10*time.Second, testSetup.assertTimeout); err != nil {
		testCase.WriteFailure("error while asserting: %v", err)
		return
	}
}

func packageManagementTestCase(ctx context.Context, testSetup *guestPolicyTestSetup, tests chan *junitxml.TestCase, wg *sync.WaitGroup, logger *log.Logger, regex *regexp.Regexp, testProjectConfig *testconfig.Project) {
	defer wg.Done()

	var logwg sync.WaitGroup

	tc, err := getTestCaseFromTestSetUp(testSetup)
	if err != nil {
		logger.Fatalf("invalid testcase: %+v", err)
		return
	}
	if tc.FilterTestCase(regex) {
		tc.Finish(tests)
	} else {
		logger.Printf("Running TestCase %q", tc.Name)
		runTest(ctx, tc, testSetup, logger, &logwg, testProjectConfig)
		tc.Finish(tests)
		logger.Printf("TestCase %q finished in %fs", tc.Name, tc.Time)
	}

	logwg.Wait()
}

// factory method to get testcase from the testsetup
func getTestCaseFromTestSetUp(testSetup *guestPolicyTestSetup) (*junitxml.TestCase, error) {
	var tc *junitxml.TestCase

	switch testSetup.testName {
	case packageInstallFunction:
		tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package installation] [%s]", path.Base(testSetup.image)))
	case packageRemovalFunction:
		tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package removal] [%s]", path.Base(testSetup.image)))
	case packageInstallFromNewRepoFunction:
		tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Add a new package from new repository] [%s]", path.Base(testSetup.image)))
	case packageUpdateFunction:
		tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package update] [%s]", path.Base(testSetup.image)))
	case packageNoUpdateFunction:
		tc = junitxml.NewTestCase(testSuiteName, fmt.Sprintf("[Package install doesn't update] [%s]", path.Base(testSetup.image)))
	default:
		return nil, fmt.Errorf("unknown test function name: %s", testSetup.testName)
	}

	return tc, nil
}

func cleanupGuestPolicy(ctx context.Context, testCase *junitxml.TestCase, gp *osconfigpb.GuestPolicy) {
	client, err := gcpclients.GetOsConfigClientV1alpha2()
	if err != nil {
		testCase.WriteFailure(fmt.Sprintf("Error while deleting osconfig: %s", utils.GetStatusFromError(err)))
	}

	if err := client.DeleteGuestPolicy(ctx, &osconfigpb.DeleteGuestPolicyRequest{Name: gp.GetName()}); err != nil {
		testCase.WriteFailure(fmt.Sprintf("Error while deleting osconfig: %s", utils.GetStatusFromError(err)))
	}
}
<|endoftext|>