<commit_before>/*
Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sqltypes

import (
	"testing"

	querypb "vitess.io/vitess/go/vt/proto/query"
)

func TestTypeValues(t *testing.T) {
	testcases := []struct {
		defined  querypb.Type
		expected int
	}{{
		defined:  Null,
		expected: 0,
	}, {
		defined:  Int8,
		expected: 1 | flagIsIntegral,
	}, {
		defined:  Uint8,
		expected: 2 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Int16,
		expected: 3 | flagIsIntegral,
	}, {
		defined:  Uint16,
		expected: 4 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Int24,
		expected: 5 | flagIsIntegral,
	}, {
		defined:  Uint24,
		expected: 6 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Int32,
		expected: 7 | flagIsIntegral,
	}, {
		defined:  Uint32,
		expected: 8 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Int64,
		expected: 9 | flagIsIntegral,
	}, {
		defined:  Uint64,
		expected: 10 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Float32,
		expected: 11 | flagIsFloat,
	}, {
		defined:  Float64,
		expected: 12 | flagIsFloat,
	}, {
		defined:  Timestamp,
		expected: 13 | flagIsQuoted,
	}, {
		defined:  Date,
		expected: 14 | flagIsQuoted,
	}, {
		defined:  Time,
		expected: 15 | flagIsQuoted,
	}, {
		defined:  Datetime,
		expected: 16 | flagIsQuoted,
	}, {
		defined:  Year,
		expected: 17 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Decimal,
		expected: 18,
	}, {
		defined:  Text,
		expected: 19 | flagIsQuoted | flagIsText,
	}, {
		defined:  Blob,
		expected: 20 | flagIsQuoted | flagIsBinary,
	}, {
		defined:  VarChar,
		expected: 21 | flagIsQuoted | flagIsText,
	}, {
		defined:  VarBinary,
		expected: 22 | flagIsQuoted | flagIsBinary,
	}, {
		defined:  Char,
		expected: 23 | flagIsQuoted | flagIsText,
	}, {
		defined:  Binary,
		expected: 24 | flagIsQuoted | flagIsBinary,
	}, {
		defined:  Bit,
		expected: 25 | flagIsQuoted,
	}, {
		defined:  Enum,
		expected: 26 | flagIsQuoted,
	}, {
		defined:  Set,
		expected: 27 | flagIsQuoted,
	}, {
		defined:  Geometry,
		expected: 29 | flagIsQuoted,
	}, {
		defined:  TypeJSON,
		expected: 30 | flagIsQuoted,
	}, {
		defined:  Expression,
		expected: 31,
	}}
	for _, tcase := range testcases {
		if int(tcase.defined) != tcase.expected {
			t.Errorf("Type %s: %d, want: %d", tcase.defined, int(tcase.defined), tcase.expected)
		}
	}
}

// TestCategory verifies that the type categorizations
// are non-overlapping and complete.
func TestCategory(t *testing.T) {
	alltypes := []querypb.Type{
		Null,
		Int8,
		Uint8,
		Int16,
		Uint16,
		Int24,
		Uint24,
		Int32,
		Uint32,
		Int64,
		Uint64,
		Float32,
		Float64,
		Timestamp,
		Date,
		Time,
		Datetime,
		Year,
		Decimal,
		Text,
		Blob,
		VarChar,
		VarBinary,
		Char,
		Binary,
		Bit,
		Enum,
		Set,
		Geometry,
		TypeJSON,
		Expression,
	}
	for _, typ := range alltypes {
		matched := false
		if IsSigned(typ) {
			if !IsIntegral(typ) {
				t.Errorf("Signed type %v is not an integral", typ)
			}
			matched = true
		}
		if IsUnsigned(typ) {
			if !IsIntegral(typ) {
				t.Errorf("Unsigned type %v is not an integral", typ)
			}
			if matched {
				t.Errorf("%v matched more than one category", typ)
			}
			matched = true
		}
		if IsFloat(typ) {
			if matched {
				t.Errorf("%v matched more than one category", typ)
			}
			matched = true
		}
		if IsQuoted(typ) {
			if matched {
				t.Errorf("%v matched more than one category", typ)
			}
			matched = true
		}
		if typ == Null || typ == Decimal || typ == Expression || typ == Bit {
			if matched {
				t.Errorf("%v matched more than one category", typ)
			}
			matched = true
		}
		if !matched {
			t.Errorf("%v matched no category", typ)
		}
	}
}

func TestIsFunctions(t *testing.T) {
	if IsIntegral(Null) {
		t.Error("Null: IsIntegral, must be false")
	}
	if !IsIntegral(Int64) {
		t.Error("Int64: !IsIntegral, must be true")
	}
	if IsSigned(Uint64) {
		t.Error("Uint64: IsSigned, must be false")
	}
	if !IsSigned(Int64) {
		t.Error("Int64: !IsSigned, must be true")
	}
	if IsUnsigned(Int64) {
		t.Error("Int64: IsUnsigned, must be false")
	}
	if !IsUnsigned(Uint64) {
		t.Error("Uint64: !IsUnsigned, must be true")
	}
	if IsFloat(Int64) {
		t.Error("Int64: IsFloat, must be false")
	}
	if !IsFloat(Float64) {
		t.Error("Uint64: !IsFloat, must be true")
	}
	if IsQuoted(Int64) {
		t.Error("Int64: IsQuoted, must be false")
	}
	if !IsQuoted(Binary) {
		t.Error("Binary: !IsQuoted, must be true")
	}
	if IsText(Int64) {
		t.Error("Int64: IsText, must be false")
	}
	if !IsText(Char) {
		t.Error("Char: !IsText, must be true")
	}
	if IsBinary(Int64) {
		t.Error("Int64: IsBinary, must be false")
	}
	if !IsBinary(Binary) {
		t.Error("Char: !IsBinary, must be true")
	}
	if !isNumber(Int64) {
		t.Error("Int64: !isNumber, must be true")
	}
}

func TestTypeToMySQL(t *testing.T) {
	v, f := TypeToMySQL(Bit)
	if v != 16 {
		t.Errorf("Bit: %d, want 16", v)
	}
	if f != mysqlUnsigned {
		t.Errorf("Bit flag: %x, want %x", f, mysqlUnsigned)
	}
	v, f = TypeToMySQL(Date)
	if v != 10 {
		t.Errorf("Bit: %d, want 10", v)
	}
	if f != mysqlBinary {
		t.Errorf("Bit flag: %x, want %x", f, mysqlBinary)
	}
}

func TestMySQLToType(t *testing.T) {
	testcases := []struct {
		intype  int64
		inflags int64
		outtype querypb.Type
	}{{
		intype:  1,
		outtype: Int8,
	}, {
		intype:  1,
		inflags: mysqlUnsigned,
		outtype: Uint8,
	}, {
		intype:  2,
		outtype: Int16,
	}, {
		intype:  2,
		inflags: mysqlUnsigned,
		outtype: Uint16,
	}, {
		intype:  3,
		outtype: Int32,
	}, {
		intype:  3,
		inflags: mysqlUnsigned,
		outtype: Uint32,
	}, {
		intype:  4,
		outtype: Float32,
	}, {
		intype:  5,
		outtype: Float64,
	}, {
		intype:  6,
		outtype: Null,
	}, {
		intype:  7,
		outtype: Timestamp,
	}, {
		intype:  8,
		outtype: Int64,
	}, {
		intype:  8,
		inflags: mysqlUnsigned,
		outtype: Uint64,
	}, {
		intype:  9,
		outtype: Int24,
	}, {
		intype:  9,
		inflags: mysqlUnsigned,
		outtype: Uint24,
	}, {
		intype:  10,
		outtype: Date,
	}, {
		intype:  11,
		outtype: Time,
	}, {
		intype:  12,
		outtype: Datetime,
	}, {
		intype:  13,
		outtype: Year,
	}, {
		intype:  16,
		outtype: Bit,
	}, {
		intype:  245,
		outtype: TypeJSON,
	}, {
		intype:  246,
		outtype: Decimal,
	}, {
		intype:  249,
		outtype: Text,
	}, {
		intype:  250,
		outtype: Text,
	}, {
		intype:  251,
		outtype: Text,
	}, {
		intype:  252,
		outtype: Text,
	}, {
		intype:  252,
		inflags: mysqlBinary,
		outtype: Blob,
	}, {
		intype:  253,
		outtype: VarChar,
	}, {
		intype:  253,
		inflags: mysqlBinary,
		outtype: VarBinary,
	}, {
		intype:  254,
		outtype: Char,
	}, {
		intype:  254,
		inflags: mysqlBinary,
		outtype: Binary,
	}, {
		intype:  254,
		inflags: mysqlEnum,
		outtype: Enum,
	}, {
		intype:  254,
		inflags: mysqlSet,
		outtype: Set,
	}, {
		intype:  255,
		outtype: Geometry,
	}, {
		// Binary flag must be ignored.
		intype:  8,
		inflags: mysqlUnsigned | mysqlBinary,
		outtype: Uint64,
	}, {
		// Unsigned flag must be ignored
		intype:  252,
		inflags: mysqlUnsigned | mysqlBinary,
		outtype: Blob,
	}}
	for _, tcase := range testcases {
		got, err := MySQLToType(tcase.intype, tcase.inflags)
		if err != nil {
			t.Error(err)
		}
		if got != tcase.outtype {
			t.Errorf("MySQLToType(%d, %x): %v, want %v", tcase.intype, tcase.inflags, got, tcase.outtype)
		}
	}
}

func TestTypeError(t *testing.T) {
	_, err := MySQLToType(17, 0)
	want := "unsupported type: 17"
	if err == nil || err.Error() != want {
		t.Errorf("MySQLToType: %v, want %s", err, want)
	}
}
<commit_msg>fix-test<commit_after>/*
Copyright 2017 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package sqltypes

import (
	"testing"

	querypb "vitess.io/vitess/go/vt/proto/query"
)

func TestTypeValues(t *testing.T) {
	testcases := []struct {
		defined  querypb.Type
		expected int
	}{{
		defined:  Null,
		expected: 0,
	}, {
		defined:  Int8,
		expected: 1 | flagIsIntegral,
	}, {
		defined:  Uint8,
		expected: 2 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Int16,
		expected: 3 | flagIsIntegral,
	}, {
		defined:  Uint16,
		expected: 4 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Int24,
		expected: 5 | flagIsIntegral,
	}, {
		defined:  Uint24,
		expected: 6 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Int32,
		expected: 7 | flagIsIntegral,
	}, {
		defined:  Uint32,
		expected: 8 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Int64,
		expected: 9 | flagIsIntegral,
	}, {
		defined:  Uint64,
		expected: 10 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Float32,
		expected: 11 | flagIsFloat,
	}, {
		defined:  Float64,
		expected: 12 | flagIsFloat,
	}, {
		defined:  Timestamp,
		expected: 13 | flagIsQuoted,
	}, {
		defined:  Date,
		expected: 14 | flagIsQuoted,
	}, {
		defined:  Time,
		expected: 15 | flagIsQuoted,
	}, {
		defined:  Datetime,
		expected: 16 | flagIsQuoted,
	}, {
		defined:  Year,
		expected: 17 | flagIsIntegral | flagIsUnsigned,
	}, {
		defined:  Decimal,
		expected: 18,
	}, {
		defined:  Text,
		expected: 19 | flagIsQuoted | flagIsText,
	}, {
		defined:  Blob,
		expected: 20 | flagIsQuoted | flagIsBinary,
	}, {
		defined:  VarChar,
		expected: 21 | flagIsQuoted | flagIsText,
	}, {
		defined:  VarBinary,
		expected: 22 | flagIsQuoted | flagIsBinary,
	}, {
		defined:  Char,
		expected: 23 | flagIsQuoted | flagIsText,
	}, {
		defined:  Binary,
		expected: 24 | flagIsQuoted | flagIsBinary,
	}, {
		defined:  Bit,
		expected: 25 | flagIsQuoted,
	}, {
		defined:  Enum,
		expected: 26 | flagIsQuoted,
	}, {
		defined:  Set,
		expected: 27 | flagIsQuoted,
	}, {
		defined:  Geometry,
		expected: 29 | flagIsQuoted,
	}, {
		defined:  TypeJSON,
		expected: 30 | flagIsQuoted,
	}, {
		defined:  Expression,
		expected: 31,
	}}
	for _, tcase := range testcases {
		if int(tcase.defined) != tcase.expected {
			t.Errorf("Type %s: %d, want: %d", tcase.defined, int(tcase.defined), tcase.expected)
		}
	}
}

// TestCategory verifies that the type categorizations
// are non-overlapping and complete.
func TestCategory(t *testing.T) {
	alltypes := []querypb.Type{
		Null,
		Int8,
		Uint8,
		Int16,
		Uint16,
		Int24,
		Uint24,
		Int32,
		Uint32,
		Int64,
		Uint64,
		Float32,
		Float64,
		Timestamp,
		Date,
		Time,
		Datetime,
		Year,
		Decimal,
		Text,
		Blob,
		VarChar,
		VarBinary,
		Char,
		Binary,
		Bit,
		Enum,
		Set,
		Geometry,
		TypeJSON,
		Expression,
	}
	for _, typ := range alltypes {
		matched := false
		if IsSigned(typ) {
			if !IsIntegral(typ) {
				t.Errorf("Signed type %v is not an integral", typ)
			}
			matched = true
		}
		if IsUnsigned(typ) {
			if !IsIntegral(typ) {
				t.Errorf("Unsigned type %v is not an integral", typ)
			}
			if matched {
				t.Errorf("%v matched more than one category", typ)
			}
			matched = true
		}
		if IsFloat(typ) {
			if matched {
				t.Errorf("%v matched more than one category", typ)
			}
			matched = true
		}
		if IsQuoted(typ) {
			if matched {
				t.Errorf("%v matched more than one category", typ)
			}
			matched = true
		}
		if typ == Null || typ == Decimal || typ == Expression || typ == Bit {
			if matched {
				t.Errorf("%v matched more than one category", typ)
			}
			matched = true
		}
		if !matched {
			t.Errorf("%v matched no category", typ)
		}
	}
}

func TestIsFunctions(t *testing.T) {
	if IsIntegral(Null) {
		t.Error("Null: IsIntegral, must be false")
	}
	if !IsIntegral(Int64) {
		t.Error("Int64: !IsIntegral, must be true")
	}
	if IsSigned(Uint64) {
		t.Error("Uint64: IsSigned, must be false")
	}
	if !IsSigned(Int64) {
		t.Error("Int64: !IsSigned, must be true")
	}
	if IsUnsigned(Int64) {
		t.Error("Int64: IsUnsigned, must be false")
	}
	if !IsUnsigned(Uint64) {
		t.Error("Uint64: !IsUnsigned, must be true")
	}
	if IsFloat(Int64) {
		t.Error("Int64: IsFloat, must be false")
	}
	if !IsFloat(Float64) {
		t.Error("Uint64: !IsFloat, must be true")
	}
	if IsQuoted(Int64) {
		t.Error("Int64: IsQuoted, must be false")
	}
	if !IsQuoted(Binary) {
		t.Error("Binary: !IsQuoted, must be true")
	}
	if IsText(Int64) {
		t.Error("Int64: IsText, must be false")
	}
	if !IsText(Char) {
		t.Error("Char: !IsText, must be true")
	}
	if IsBinary(Int64) {
		t.Error("Int64: IsBinary, must be false")
	}
	if !IsBinary(Binary) {
		t.Error("Char: !IsBinary, must be true")
	}
	if !isNumber(Int64) {
		t.Error("Int64: !isNumber, must be true")
	}
}

func TestTypeToMySQL(t *testing.T) {
	v, f := TypeToMySQL(Bit)
	if v != 16 {
		t.Errorf("Bit: %d, want 16", v)
	}
	if f != mysqlUnsigned {
		t.Errorf("Bit flag: %x, want %x", f, mysqlUnsigned)
	}
	v, f = TypeToMySQL(Date)
	if v != 10 {
		t.Errorf("Bit: %d, want 10", v)
	}
	if f != mysqlBinary {
		t.Errorf("Bit flag: %x, want %x", f, mysqlBinary)
	}
}

func TestMySQLToType(t *testing.T) {
	testcases := []struct {
		intype  int64
		inflags int64
		outtype querypb.Type
	}{{
		intype:  1,
		outtype: Int8,
	}, {
		intype:  1,
		inflags: mysqlUnsigned,
		outtype: Uint8,
	}, {
		intype:  2,
		outtype: Int16,
	}, {
		intype:  2,
		inflags: mysqlUnsigned,
		outtype: Uint16,
	}, {
		intype:  3,
		outtype: Int32,
	}, {
		intype:  3,
		inflags: mysqlUnsigned,
		outtype: Uint32,
	}, {
		intype:  4,
		outtype: Float32,
	}, {
		intype:  5,
		outtype: Float64,
	}, {
		intype:  6,
		outtype: Null,
	}, {
		intype:  7,
		outtype: Timestamp,
	}, {
		intype:  8,
		outtype: Int64,
	}, {
		intype:  8,
		inflags: mysqlUnsigned,
		outtype: Uint64,
	}, {
		intype:  9,
		outtype: Int24,
	}, {
		intype:  9,
		inflags: mysqlUnsigned,
		outtype: Uint24,
	}, {
		intype:  10,
		outtype: Date,
	}, {
		intype:  11,
		outtype: Time,
	}, {
		intype:  12,
		outtype: Datetime,
	}, {
		intype:  13,
		outtype: Year,
	}, {
		intype:  16,
		outtype: Bit,
	}, {
		intype:  245,
		outtype: TypeJSON,
	}, {
		intype:  246,
		outtype: Decimal,
	}, {
		intype:  249,
		outtype: Text,
	}, {
		intype:  250,
		outtype: Text,
	}, {
		intype:  251,
		outtype: Text,
	}, {
		intype:  252,
		outtype: Text,
	}, {
		intype:  252,
		inflags: mysqlBinary,
		outtype: Blob,
	}, {
		intype:  253,
		outtype: VarChar,
	}, {
		intype:  253,
		inflags: mysqlBinary,
		outtype: VarBinary,
	}, {
		intype:  254,
		outtype: Char,
	}, {
		intype:  254,
		inflags: mysqlBinary,
		outtype: Binary,
	}, {
		intype:  254,
		inflags: mysqlEnum,
		outtype: Enum,
	}, {
		intype:  254,
		inflags: mysqlSet,
		outtype: Set,
	}, {
		intype:  255,
		outtype: Geometry,
	}, {
		// Binary flag must be ignored.
		intype:  8,
		inflags: mysqlUnsigned | mysqlBinary,
		outtype: Uint64,
	}, {
		// Unsigned flag must be ignored
		intype:  252,
		inflags: mysqlUnsigned | mysqlBinary,
		outtype: Blob,
	}}
	for _, tcase := range testcases {
		got, err := MySQLToType(tcase.intype, tcase.inflags)
		if err != nil {
			t.Error(err)
		}
		if got != tcase.outtype {
			t.Errorf("MySQLToType(%d, %x): %v, want %v", tcase.intype, tcase.inflags, got, tcase.outtype)
		}
	}
}

func TestTypeError(t *testing.T) {
	_, err := MySQLToType(50, 0)
	want := "unsupported type: 50"
	if err == nil || err.Error() != want {
		t.Errorf("MySQLToType: %v, want %s", err, want)
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2020 the Octant contributors. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 *
 */

package workloads

import (
	"context"
	"fmt"
	"path"

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

	"github.com/vmware-tanzu/octant/internal/describer"
	"github.com/vmware-tanzu/octant/internal/octant"
	"github.com/vmware-tanzu/octant/internal/resourceviewer"
	"github.com/vmware-tanzu/octant/pkg/view/component"
)

type DetailDescriber struct{}

var _ describer.Describer = (*DetailDescriber)(nil)

func NewDetailDescriber() (*DetailDescriber, error) {
	d := &DetailDescriber{}

	return d, nil
}

func (d *DetailDescriber) loadWorkloads(ctx context.Context, namespace string, options describer.Options) ([]octant.Workload, error) {
	pml, err := octant.NewClusterPodMetricsLoader(options.Dash.ClusterClient())
	if err != nil {
		return nil, fmt.Errorf("create pod metrics loader")
	}

	loader, err := octant.NewClusterWorkloadLoader(options.Dash.ObjectStore(), pml)
	if err != nil {
		return nil, fmt.Errorf("create workload loader")
	}

	workloads, err := loader.Load(ctx, namespace)
	if err != nil {
		return nil, fmt.Errorf("load workloads")
	}

	return workloads, nil
}

func (d *DetailDescriber) Describe(ctx context.Context, namespace string, options describer.Options) (component.ContentResponse, error) {
	name := options.Fields["name"]

	workloads, err := d.loadWorkloads(ctx, namespace, options)
	if err != nil {
		cr := d.createResponse(
			component.NewError(component.TitleFromString("Unable to load workloads"), err),
		)
		return cr, nil
	}

	found := false
	var cur octant.Workload
	for _, workload := range workloads {
		if workload.Name != name {
			continue
		}

		found = true
		cur = workload
	}

	if !found {
		cr := d.createResponse(
			component.NewError(component.TitleFromString("Workload not found"),
				fmt.Errorf("unable to find workload %s", name)),
		)
		return cr, nil
	}

	layout := component.NewFlexLayout("Workload layout")

	summary, err := octant.CreateWorkloadSummary(&cur, component.DonutChartSizeSmall)
	if err != nil {
		cr := d.createResponse(
			component.NewError(component.TitleFromString("Create summary for workload"), err),
		)
		return cr, nil
	}

	workloadName := fmt.Sprintf(`
### %s
_%s_
`, cur.Name, cur.Owner.GroupVersionKind())

	headerSection := component.FlexLayoutSection{
		{
			Width: component.WidthQuarter,
			View:  component.NewMarkdownText(workloadName),
		},
		{
			Width: component.WidthQuarter,
			View:  summary.Summary,
		},
	}

	if summary.MetricsEnabled {
		headerSection = append(headerSection, []component.FlexLayoutItem{
			{
				Width: component.WidthQuarter,
				View:  summary.Memory,
			},
			{
				Width: component.WidthQuarter,
				View:  summary.CPU,
			},
		}...)
	}

	var objects []*unstructured.Unstructured
	for _, pod := range cur.Pods().Items {
		objects = append(objects, &pod)
	}

	rv, err := resourceviewer.Create(ctx, options.Dash, options.Queryer, objects...)
	if err != nil {
		cr := d.createResponse(
			component.NewError(component.TitleFromString("Unable to create resource viewer"), err),
		)
		return cr, nil
	}

	viewerSection := component.FlexLayoutSection{
		{
			Width: component.WidthFull,
			View:  rv,
		},
	}
	layout.AddSections(headerSection, viewerSection)

	cr := d.createResponse(
		layout,
	)

	return cr, nil
}

func (d *DetailDescriber) createResponse(components ...component.Component) component.ContentResponse {
	cr := component.ContentResponse{
		Title:      component.TitleFromString("Workload X"),
		Components: components,
	}

	return cr
}

func (d *DetailDescriber) PathFilters() []describer.PathFilter {
	return []describer.PathFilter{
		*describer.NewPathFilter(path.Join("/detail", describer.ResourceNameRegex), d),
	}
}

func (d DetailDescriber) Reset(_ context.Context) error {
	return nil
}
<commit_msg>Fixed Resource Viewer bug with missing pods<commit_after>/*
 * Copyright (c) 2020 the Octant contributors. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 *
 */

package workloads

import (
	"context"
	"fmt"
	"path"

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

	"github.com/vmware-tanzu/octant/internal/describer"
	"github.com/vmware-tanzu/octant/internal/octant"
	"github.com/vmware-tanzu/octant/internal/resourceviewer"
	"github.com/vmware-tanzu/octant/pkg/view/component"
)

type DetailDescriber struct{}

var _ describer.Describer = (*DetailDescriber)(nil)

func NewDetailDescriber() (*DetailDescriber, error) {
	d := &DetailDescriber{}

	return d, nil
}

func (d *DetailDescriber) loadWorkloads(ctx context.Context, namespace string, options describer.Options) ([]octant.Workload, error) {
	pml, err := octant.NewClusterPodMetricsLoader(options.Dash.ClusterClient())
	if err != nil {
		return nil, fmt.Errorf("create pod metrics loader")
	}

	loader, err := octant.NewClusterWorkloadLoader(options.Dash.ObjectStore(), pml)
	if err != nil {
		return nil, fmt.Errorf("create workload loader")
	}

	workloads, err := loader.Load(ctx, namespace)
	if err != nil {
		return nil, fmt.Errorf("load workloads")
	}

	return workloads, nil
}

func (d *DetailDescriber) Describe(ctx context.Context, namespace string, options describer.Options) (component.ContentResponse, error) {
	name := options.Fields["name"]

	workloads, err := d.loadWorkloads(ctx, namespace, options)
	if err != nil {
		cr := d.createResponse(
			component.NewError(component.TitleFromString("Unable to load workloads"), err),
		)
		return cr, nil
	}

	found := false
	var cur octant.Workload
	for _, workload := range workloads {
		if workload.Name != name {
			continue
		}

		found = true
		cur = workload
	}

	if !found {
		cr := d.createResponse(
			component.NewError(component.TitleFromString("Workload not found"),
				fmt.Errorf("unable to find workload %s", name)),
		)
		return cr, nil
	}

	layout := component.NewFlexLayout("Workload layout")

	summary, err := octant.CreateWorkloadSummary(&cur, component.DonutChartSizeSmall)
	if err != nil {
		cr := d.createResponse(
			component.NewError(component.TitleFromString("Create summary for workload"), err),
		)
		return cr, nil
	}

	workloadName := fmt.Sprintf(`
### %s
_%s_
`, cur.Name, cur.Owner.GroupVersionKind())

	headerSection := component.FlexLayoutSection{
		{
			Width: component.WidthQuarter,
			View:  component.NewMarkdownText(workloadName),
		},
		{
			Width: component.WidthQuarter,
			View:  summary.Summary,
		},
	}

	if summary.MetricsEnabled {
		headerSection = append(headerSection, []component.FlexLayoutItem{
			{
				Width: component.WidthQuarter,
				View:  summary.Memory,
			},
			{
				Width: component.WidthQuarter,
				View:  summary.CPU,
			},
		}...)
	}

	var objects []*unstructured.Unstructured
	pods := cur.Pods().Items

	for i := 0; i < len(pods); i++ {
		objects = append(objects, &pods[i])
	}

	rv, err := resourceviewer.Create(ctx, options.Dash, options.Queryer, objects...)
	if err != nil {
		cr := d.createResponse(
			component.NewError(component.TitleFromString("Unable to create resource viewer"), err),
		)
		return cr, nil
	}

	viewerSection := component.FlexLayoutSection{
		{
			Width: component.WidthFull,
			View:  rv,
		},
	}
	layout.AddSections(headerSection, viewerSection)

	cr := d.createResponse(
		layout,
	)

	return cr, nil
}

func (d *DetailDescriber) createResponse(components ...component.Component) component.ContentResponse {
	cr := component.ContentResponse{
		Title:      component.TitleFromString(""),
		Components: components,
	}

	return cr
}

func (d *DetailDescriber) PathFilters() []describer.PathFilter {
	return []describer.PathFilter{
		*describer.NewPathFilter(path.Join("/detail", describer.ResourceNameRegex), d),
	}
}

func (d DetailDescriber) Reset(_ context.Context) error {
	return nil
}
<|endoftext|>


<commit_before>package tracker

// http://bittorrent.org/beps/bep_0015.html
// http://xbtt.sourceforge.net/udp_tracker_protocol.html
// http://www.rasterbar.com/products/libtorrent/udp_tracker_protocol.html

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"math/rand"
	"net"
	"sync"
	"time"

	"github.com/cenkalti/backoff"

	"github.com/cenkalti/rain/internal/protocol"
)

const connectionIDMagic = 0x41727101980
const connectionIDInterval = time.Minute

type action int32

// UDP tracker Actions
const (
	connect action = iota
	announce
	scrape
	errorAction
)

type transaction struct {
	request  udpReqeust
	response []byte
	err      error
	done     chan struct{}
}

func newTransaction(req udpReqeust) *transaction {
	req.SetTransactionID(rand.Int31())
	return &transaction{
		request: req,
		done:    make(chan struct{}),
	}
}

func (t *transaction) ID() int32 { return t.request.GetTransactionID() }
func (t *transaction) Done()     { close(t.done) }

type udpTracker struct {
	*trackerBase
	conn          *net.UDPConn
	dialMutex     sync.Mutex
	connected     bool
	transactions  map[int32]*transaction
	transactionsM sync.Mutex
	writeC        chan *transaction
}

func newUDPTracker(b *trackerBase) *udpTracker {
	return &udpTracker{
		trackerBase:  b,
		transactions: make(map[int32]*transaction),
		writeC:       make(chan *transaction),
	}
}

func (t *udpTracker) dial() error {
	serverAddr, err := net.ResolveUDPAddr("udp", t.url.Host)
	if err != nil {
		return err
	}
	t.conn, err = net.DialUDP("udp", nil, serverAddr)
	if err != nil {
		return err
	}
	go t.readLoop()
	go t.writeLoop()
	return nil
}

// Close the tracker connection.
func (t *udpTracker) Close() error {
	close(t.writeC)
	return t.conn.Close()
}

// readLoop reads datagrams from connection, finds the transaction and
// sends the bytes to the transaction's response channel.
func (t *udpTracker) readLoop() {
	// Read buffer must be big enough to hold a UDP packet of maximum expected size.
	// Current value is: 320 = 20 + 50*6 (AnnounceResponse with 50 peers)
	buf := make([]byte, 320)
	for {
		n, err := t.conn.Read(buf)
		if err != nil {
			t.log.Error(err)
			if nerr, ok := err.(net.Error); ok && !nerr.Temporary() {
				t.log.Debug("End of tracker read loop")
				return
			}
			continue
		}
		t.log.Debug("Read ", n, " bytes")

		var header udpMessageHeader
		if n < binary.Size(header) {
			t.log.Error("response is too small")
			continue
		}

		err = binary.Read(bytes.NewReader(buf), binary.BigEndian, &header)
		if err != nil {
			t.log.Error(err)
			continue
		}

		t.transactionsM.Lock()
		trx, ok := t.transactions[header.TransactionID]
		delete(t.transactions, header.TransactionID)
		t.transactionsM.Unlock()
		if !ok {
			t.log.Errorln("unexpected transaction_id:", header.TransactionID)
			continue
		}

		// Tracker has sent and error.
		if header.Action == errorAction {
			// The part after the header is the error message.
			trx.err = Error(buf[binary.Size(header):])
			trx.Done()
			continue
		}

		// Copy data into a new slice because buf will be overwritten at next read.
		trx.response = make([]byte, n)
		copy(trx.response, buf)
		trx.Done()
	}
}

// writeLoop receives a request from t.transactionC, sets a random TransactionID
// and sends it to the tracker.
func (t *udpTracker) writeLoop() {
	var connectionID int64
	var connectionIDtime time.Time

	for trx := range t.writeC {
		if time.Since(connectionIDtime) > connectionIDInterval {
			connectionID = t.connect()
			connectionIDtime = time.Now()
		}
		trx.request.SetConnectionID(connectionID)

		t.writeTrx(trx)
	}
}

func (t *udpTracker) writeTrx(trx *transaction) {
	t.log.Debugln("Writing transaction. ID:", trx.ID())
	_, err := trx.request.WriteTo(t.conn)
	if err != nil {
		t.log.Error(err)
	}
}

// connect sends a connectRequest and returns a ConnectionID given by the tracker.
// On error, it backs off with the algorithm described in BEP15 and retries.
// It does not return until tracker sends a ConnectionID.
func (t *udpTracker) connect() int64 {
	req := new(connectRequest)
	req.SetAction(connect)
	req.SetConnectionID(connectionIDMagic)

	trx := newTransaction(req)

	for {
		data, err := t.retryTransaction(t.writeTrx, trx, nil) // Does not return until transaction is completed.
		if err != nil {
			t.log.Error(err)
			continue
		}

		var response connectResponse
		err = binary.Read(bytes.NewReader(data), binary.BigEndian, &response)
		if err != nil {
			t.log.Error(err)
			continue
		}

		if response.Action != connect {
			t.log.Error("invalid action in connect response")
			continue
		}

		t.log.Debugf("connect Response: %#v\n", response)
		return response.ConnectionID
	}
}

func (t *udpTracker) retryTransaction(f func(*transaction), trx *transaction, cancel <-chan struct{}) ([]byte, error) {
	t.transactionsM.Lock()
	t.transactions[trx.ID()] = trx
	t.transactionsM.Unlock()

	ticker := backoff.NewTicker(UDPBackOff())
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			f(trx)
		case <-trx.done:
			// transaction is deleted in readLoop()
			return trx.response, trx.err
		case <-cancel:
			t.transactionsM.Lock()
			delete(t.transactions, trx.ID())
			t.transactionsM.Unlock()
			return nil, RequestCancelled
		}
	}
}

func (t *udpTracker) sendTransaction(trx *transaction, cancel <-chan struct{}) ([]byte, error) {
	f := func(trx *transaction) { t.writeC <- trx }
	return t.retryTransaction(f, trx, cancel)
}

func (t *udpTracker) Announce(transfer Transfer, e Event, cancel <-chan struct{}) (*AnnounceResponse, error) {
	t.dialMutex.Lock()
	if !t.connected {
		err := t.dial()
		if err != nil {
			t.dialMutex.Unlock()
			return nil, err
		}
		t.connected = true
	}
	t.dialMutex.Unlock()

	request := &announceRequest{
		InfoHash:   transfer.InfoHash(),
		PeerID:     t.peerID,
		Event:      e,
		IP:         0, // Tracker uses sender of this UDP packet.
		Key:        0, // TODO set it
		NumWant:    NumWant,
		Port:       t.port,
		Extensions: 0,
	}
	request.SetAction(announce)

	request2 := &transferAnnounceRequest{
		transfer:        transfer,
		announceRequest: request,
		urlData:         t.url.Path + "?" + t.url.RawQuery,
	}
	trx := newTransaction(request2)

	// t.request may block, that's why we pass cancel as argument.
	reply, err := t.sendTransaction(trx, cancel)
	if err != nil {
		if err, ok := err.(Error); ok {
			return &AnnounceResponse{Error: err}, nil
		}
		return nil, err
	}

	response, peers, err := t.parseAnnounceResponse(reply)
	if err != nil {
		return nil, err
	}
	t.log.Debugf("Announce response: %#v", response)

	return &AnnounceResponse{
		Interval: time.Duration(response.Interval) * time.Second,
		Leechers: response.Leechers,
		Seeders:  response.Seeders,
		Peers:    peers,
	}, nil
}

func (t *udpTracker) parseAnnounceResponse(data []byte) (*announceResponse, []Peer, error) {
	response := new(announceResponse)
	if len(data) < binary.Size(response) {
		return nil, nil, errors.New("response is too small")
	}

	reader := bytes.NewReader(data)

	err := binary.Read(reader, binary.BigEndian, response)
	if err != nil {
		return nil, nil, err
	}
	t.log.Debugf("annouceResponse: %#v", response)

	if response.Action != announce {
		return nil, nil, errors.New("invalid action")
	}

	peers, err := t.parsePeers(reader)
	if err != nil {
		return nil, nil, err
	}

	return response, peers, nil
}

func (t *udpTracker) Scrape(transfers []Transfer) (*ScrapeResponse, error) { return nil, nil }

type udpBackOff int

func (b *udpBackOff) NextBackOff() time.Duration {
	defer func() { *b++ }()
	if *b > 8 {
		*b = 8
	}
	return time.Duration(15*(2^*b)) * time.Second
}

func (b *udpBackOff) Reset() { *b = 0 }

var UDPBackOff = func() backoff.BackOff { return new(udpBackOff) }

type udpMessage interface {
	GetAction() action
	SetAction(action)
	GetTransactionID() int32
	SetTransactionID(int32)
}

type udpReqeust interface {
	udpMessage
	GetConnectionID() int64
	SetConnectionID(int64)
	io.WriterTo
}

// udpMessageHeader implements udpMessage.
type udpMessageHeader struct {
	Action        action
	TransactionID int32
}

func (h *udpMessageHeader) GetAction() action         { return h.Action }
func (h *udpMessageHeader) SetAction(a action)        { h.Action = a }
func (h *udpMessageHeader) GetTransactionID() int32   { return h.TransactionID }
func (h *udpMessageHeader) SetTransactionID(id int32) { h.TransactionID = id }

// udpRequestHeader implements udpMessage and udpReqeust.
type udpRequestHeader struct {
	ConnectionID int64
	udpMessageHeader
}

func (h *udpRequestHeader) GetConnectionID() int64   { return h.ConnectionID }
func (h *udpRequestHeader) SetConnectionID(id int64) { h.ConnectionID = id }

type connectRequest struct {
	udpRequestHeader
}

func (r *connectRequest) WriteTo(w io.Writer) (int64, error) {
	return 0, binary.Write(w, binary.BigEndian, r)
}

type connectResponse struct {
	udpMessageHeader
	ConnectionID int64
}

type announceRequest struct {
	udpRequestHeader
	InfoHash   protocol.InfoHash
	PeerID     protocol.PeerID
	Downloaded int64
	Left       int64
	Uploaded   int64
	Event      Event
	IP         uint32
	Key        uint32
	NumWant    int32
	Port       uint16
	Extensions uint16
}

type transferAnnounceRequest struct {
	transfer Transfer
	*announceRequest
	urlData string
}

func (r *transferAnnounceRequest) WriteTo(w io.Writer) (int64, error) {
	buf := bufio.NewWriter(w)

	r.Downloaded = r.transfer.Downloaded()
	r.Uploaded = r.transfer.Uploaded()
	r.Left = r.transfer.Left()
	err := binary.Write(buf, binary.BigEndian, r.announceRequest)
	if err != nil {
		return 0, err
	}

	pos := 0
	for pos < len(r.urlData) {
		remaining := len(r.urlData) - 1 - pos
		var size int
		if remaining > 255 {
			size = 255
		} else {
			size = remaining
		}
		buf.Write([]byte{0x2, byte(size)})
		buf.WriteString(r.urlData[pos : pos+size])
		pos += size
	}

	return 0, buf.Flush()
}

type announceResponse struct {
	udpMessageHeader
	Interval int32
	Leechers int32
	Seeders  int32
}
<commit_msg>fix urldata<commit_after>package tracker

// http://bittorrent.org/beps/bep_0015.html
// http://xbtt.sourceforge.net/udp_tracker_protocol.html
// http://www.rasterbar.com/products/libtorrent/udp_tracker_protocol.html

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"math/rand"
	"net"
	"sync"
	"time"

	"github.com/cenkalti/backoff"

	"github.com/cenkalti/rain/internal/protocol"
)

const connectionIDMagic = 0x41727101980
const connectionIDInterval = time.Minute

type action int32

// UDP tracker Actions
const (
	connect action = iota
	announce
	scrape
	errorAction
)

type transaction struct {
	request  udpReqeust
	response []byte
	err      error
	done     chan struct{}
}

func newTransaction(req udpReqeust) *transaction {
	req.SetTransactionID(rand.Int31())
	return &transaction{
		request: req,
		done:    make(chan struct{}),
	}
}

func (t *transaction) ID() int32 { return t.request.GetTransactionID() }
func (t *transaction) Done()     { close(t.done) }

type udpTracker struct {
	*trackerBase
	conn          *net.UDPConn
	dialMutex     sync.Mutex
	connected     bool
	transactions  map[int32]*transaction
	transactionsM sync.Mutex
	writeC        chan *transaction
}

func newUDPTracker(b *trackerBase) *udpTracker {
	return &udpTracker{
		trackerBase:  b,
		transactions: make(map[int32]*transaction),
		writeC:       make(chan *transaction),
	}
}

func (t *udpTracker) dial() error {
	serverAddr, err := net.ResolveUDPAddr("udp", t.url.Host)
	if err != nil {
		return err
	}
	t.conn, err = net.DialUDP("udp", nil, serverAddr)
	if err != nil {
		return err
	}
	go t.readLoop()
	go t.writeLoop()
	return nil
}

// Close the tracker connection.
func (t *udpTracker) Close() error {
	close(t.writeC)
	return t.conn.Close()
}

// readLoop reads datagrams from connection, finds the transaction and
// sends the bytes to the transaction's response channel.
func (t *udpTracker) readLoop() {
	// Read buffer must be big enough to hold a UDP packet of maximum expected size.
	// Current value is: 320 = 20 + 50*6 (AnnounceResponse with 50 peers)
	buf := make([]byte, 320)
	for {
		n, err := t.conn.Read(buf)
		if err != nil {
			t.log.Error(err)
			if nerr, ok := err.(net.Error); ok && !nerr.Temporary() {
				t.log.Debug("End of tracker read loop")
				return
			}
			continue
		}
		t.log.Debug("Read ", n, " bytes")

		var header udpMessageHeader
		if n < binary.Size(header) {
			t.log.Error("response is too small")
			continue
		}

		err = binary.Read(bytes.NewReader(buf), binary.BigEndian, &header)
		if err != nil {
			t.log.Error(err)
			continue
		}

		t.transactionsM.Lock()
		trx, ok := t.transactions[header.TransactionID]
		delete(t.transactions, header.TransactionID)
		t.transactionsM.Unlock()
		if !ok {
			t.log.Errorln("unexpected transaction_id:", header.TransactionID)
			continue
		}

		// Tracker has sent and error.
		if header.Action == errorAction {
			// The part after the header is the error message.
			trx.err = Error(buf[binary.Size(header):])
			trx.Done()
			continue
		}

		// Copy data into a new slice because buf will be overwritten at next read.
		trx.response = make([]byte, n)
		copy(trx.response, buf)
		trx.Done()
	}
}

// writeLoop receives a request from t.transactionC, sets a random TransactionID
// and sends it to the tracker.
func (t *udpTracker) writeLoop() {
	var connectionID int64
	var connectionIDtime time.Time

	for trx := range t.writeC {
		if time.Since(connectionIDtime) > connectionIDInterval {
			connectionID = t.connect()
			connectionIDtime = time.Now()
		}
		trx.request.SetConnectionID(connectionID)

		t.writeTrx(trx)
	}
}

func (t *udpTracker) writeTrx(trx *transaction) {
	t.log.Debugln("Writing transaction. ID:", trx.ID())
	_, err := trx.request.WriteTo(t.conn)
	if err != nil {
		t.log.Error(err)
	}
}

// connect sends a connectRequest and returns a ConnectionID given by the tracker.
// On error, it backs off with the algorithm described in BEP15 and retries.
// It does not return until tracker sends a ConnectionID.
func (t *udpTracker) connect() int64 {
	req := new(connectRequest)
	req.SetAction(connect)
	req.SetConnectionID(connectionIDMagic)

	trx := newTransaction(req)

	for {
		data, err := t.retryTransaction(t.writeTrx, trx, nil) // Does not return until transaction is completed.
		if err != nil {
			t.log.Error(err)
			continue
		}

		var response connectResponse
		err = binary.Read(bytes.NewReader(data), binary.BigEndian, &response)
		if err != nil {
			t.log.Error(err)
			continue
		}

		if response.Action != connect {
			t.log.Error("invalid action in connect response")
			continue
		}

		t.log.Debugf("connect Response: %#v\n", response)
		return response.ConnectionID
	}
}

func (t *udpTracker) retryTransaction(f func(*transaction), trx *transaction, cancel <-chan struct{}) ([]byte, error) {
	t.transactionsM.Lock()
	t.transactions[trx.ID()] = trx
	t.transactionsM.Unlock()

	ticker := backoff.NewTicker(UDPBackOff())
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			f(trx)
		case <-trx.done:
			// transaction is deleted in readLoop()
			return trx.response, trx.err
		case <-cancel:
			t.transactionsM.Lock()
			delete(t.transactions, trx.ID())
			t.transactionsM.Unlock()
			return nil, RequestCancelled
		}
	}
}

func (t *udpTracker) sendTransaction(trx *transaction, cancel <-chan struct{}) ([]byte, error) {
	f := func(trx *transaction) { t.writeC <- trx }
	return t.retryTransaction(f, trx, cancel)
}

func (t *udpTracker) Announce(transfer Transfer, e Event, cancel <-chan struct{}) (*AnnounceResponse, error) {
	t.dialMutex.Lock()
	if !t.connected {
		err := t.dial()
		if err != nil {
			t.dialMutex.Unlock()
			return nil, err
		}
		t.connected = true
	}
	t.dialMutex.Unlock()

	request := &announceRequest{
		InfoHash:   transfer.InfoHash(),
		PeerID:     t.peerID,
		Event:      e,
		IP:         0, // Tracker uses sender of this UDP packet.
		Key:        0, // TODO set it
		NumWant:    NumWant,
		Port:       t.port,
		Extensions: 0,
	}
	request.SetAction(announce)

	request2 := &transferAnnounceRequest{
		transfer:        transfer,
		announceRequest: request,
		urlData:         t.url.RequestURI(),
	}
	trx := newTransaction(request2)

	// t.request may block, that's why we pass cancel as argument.
	reply, err := t.sendTransaction(trx, cancel)
	if err != nil {
		if err, ok := err.(Error); ok {
			return &AnnounceResponse{Error: err}, nil
		}
		return nil, err
	}

	response, peers, err := t.parseAnnounceResponse(reply)
	if err != nil {
		return nil, err
	}
	t.log.Debugf("Announce response: %#v", response)

	return &AnnounceResponse{
		Interval: time.Duration(response.Interval) * time.Second,
		Leechers: response.Leechers,
		Seeders:  response.Seeders,
		Peers:    peers,
	}, nil
}

func (t *udpTracker) parseAnnounceResponse(data []byte) (*announceResponse, []Peer, error) {
	response := new(announceResponse)
	if len(data) < binary.Size(response) {
		return nil, nil, errors.New("response is too small")
	}

	reader := bytes.NewReader(data)

	err := binary.Read(reader, binary.BigEndian, response)
	if err != nil {
		return nil, nil, err
	}
	t.log.Debugf("annouceResponse: %#v", response)

	if response.Action != announce {
		return nil, nil, errors.New("invalid action")
	}

	peers, err := t.parsePeers(reader)
	if err != nil {
		return nil, nil, err
	}

	return response, peers, nil
}

func (t *udpTracker) Scrape(transfers []Transfer) (*ScrapeResponse, error) { return nil, nil }

type udpBackOff int

func (b *udpBackOff) NextBackOff() time.Duration {
	defer func() { *b++ }()
	if *b > 8 {
		*b = 8
	}
	return time.Duration(15*(2^*b)) * time.Second
}

func (b *udpBackOff) Reset() { *b = 0 }

var UDPBackOff = func() backoff.BackOff { return new(udpBackOff) }

type udpMessage interface {
	GetAction() action
	SetAction(action)
	GetTransactionID() int32
	SetTransactionID(int32)
}

type udpReqeust interface {
	udpMessage
	GetConnectionID() int64
	SetConnectionID(int64)
	io.WriterTo
}

// udpMessageHeader implements udpMessage.
type udpMessageHeader struct {
	Action        action
	TransactionID int32
}

func (h *udpMessageHeader) GetAction() action         { return h.Action }
func (h *udpMessageHeader) SetAction(a action)        { h.Action = a }
func (h *udpMessageHeader) GetTransactionID() int32   { return h.TransactionID }
func (h *udpMessageHeader) SetTransactionID(id int32) { h.TransactionID = id }

// udpRequestHeader implements udpMessage and udpReqeust.
type udpRequestHeader struct {
	ConnectionID int64
	udpMessageHeader
}

func (h *udpRequestHeader) GetConnectionID() int64   { return h.ConnectionID }
func (h *udpRequestHeader) SetConnectionID(id int64) { h.ConnectionID = id }

type connectRequest struct {
	udpRequestHeader
}

func (r *connectRequest) WriteTo(w io.Writer) (int64, error) {
	return 0, binary.Write(w, binary.BigEndian, r)
}

type connectResponse struct {
	udpMessageHeader
	ConnectionID int64
}

type announceRequest struct {
	udpRequestHeader
	InfoHash   protocol.InfoHash
	PeerID     protocol.PeerID
	Downloaded int64
	Left       int64
	Uploaded   int64
	Event      Event
	IP         uint32
	Key        uint32
	NumWant    int32
	Port       uint16
	Extensions uint16
}

type transferAnnounceRequest struct {
	transfer Transfer
	*announceRequest
	urlData string
}

func (r *transferAnnounceRequest) WriteTo(w io.Writer) (int64, error) {
	buf := bufio.NewWriterSize(w, 98+2+255)

	r.announceRequest.Downloaded = r.transfer.Downloaded()
	r.announceRequest.Uploaded = r.transfer.Uploaded()
	r.announceRequest.Left = r.transfer.Left()
	err := binary.Write(buf, binary.BigEndian, r.announceRequest)
	if err != nil {
		return 0, err
	}

	if r.urlData != "" {
		pos := 0
		for pos < len(r.urlData) {
			remaining := len(r.urlData) - pos
			var size int
			if remaining > 255 {
				size = 255
			} else {
				size = remaining
			}
			buf.Write([]byte{0x2, byte(size)})
			buf.WriteString(r.urlData[pos : pos+size])
			pos += size
		}
	}

	return int64(buf.Buffered()), buf.Flush()
}

type announceResponse struct {
	udpMessageHeader
	Interval int32
	Leechers int32
	Seeders  int32
}
<|endoftext|>


<commit_before>package users

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"seec2/internal/gh"
	"strings"

	"github.com/google/go-github/github"
)

type User struct {
	Login string
	Name  string
}

func AuthorNameAndLogin(c *gh.Commit) *User {
	clogin := login(*c.Author.Email, *c.Author.Name, *c.SHA)
	cname := *c.Author.Name
	return &User{Login: clogin, Name: cname}
}
func CommitterNameAndLogin(c *gh.Commit) *User {
	clogin := login(*c.Committer.Email, *c.Committer.Name, *c.SHA)
	cname := *c.Committer.Name
	return &User{Login: clogin, Name: cname}
}

func Login(email string, name string) string {
	return login(email, name, "")
}

func login(email string, name string, SHA string) string {
	fmt.Printf("email='%s', name='%s'\n", email, name)
	if login := cacheLogins(email, name); login != "" {
		return login
	}
	if login := scrapPage(SHA); login != "" {
		return addToCacheLogins(email, name, login)
	}
	opts := &github.SearchOptions{Order: "desc"}
	var res *github.UsersSearchResult
	var err error
	if email != "" {
		res, _, err = gh.Client.Search.Users(email, opts)
		if err != nil {
			fmt.Printf("Unable to search user '%s': err '%v'", email, err)
			gh.GHex.Exit(1)
		}
	}
	nameNoDash := name
	if res == nil || *res.Total == 0 {
		nameNoDash = strings.Replace(name, "-", " ", -1)
		res, _, err = gh.Client.Search.Users(nameNoDash, opts)
		if err != nil {
			fmt.Printf("Unable to search user '%s': err '%v'", nameNoDash, err)
			gh.GHex.Exit(1)
		}
	}
	if res == nil || *res.Total == 0 {
		var resIssues *github.IssuesSearchResult
		issueSearch := fmt.Sprintf(`"Signed-off-by: %s <%s>"`, nameNoDash, email)
		resIssues, _, err = gh.Client.Search.Issues(issueSearch, opts)
		if err != nil {
			fmt.Printf("Unable to search issue '%s': err '%v'", issueSearch, err)
			gh.GHex.Exit(1)
		}
		if resIssues == nil || *resIssues.Total == 0 {
			return ""
		}
		issue := resIssues.Issues[0]
		return addToCacheLogins(email, name, *issue.User.Login)
	}
	if res == nil || *res.Total == 0 {
		return ""
	}
	return addToCacheLogins(email, name, *res.Users[0].Login)
}

var cacheLoginsRE = regexp.MustCompile(`^(.*)#~#(.*?)\^\((.*?)\)\^(.*?)$`)

func cacheLogins(email string, name string) string {
	login := ""
	fi, err := os.OpenFile("logins.txt", os.O_RDONLY|os.O_CREATE, 0660)
	if err != nil {
		panic(err)
	}
	// close fi on exit and check for its returned error
	defer func() {
		if err := fi.Close(); err != nil {
			panic(err)
		}
	}()
	var part []byte
	var prefix bool
	reader := bufio.NewReader(fi)
	buffer := bytes.NewBuffer(make([]byte, 0))
	for {
		if part, prefix, err = reader.ReadLine(); err != nil {
			break
		}
		buffer.Write(part)
		if !prefix {
			line := buffer.String()
			re := cacheLoginsRE.FindAllStringSubmatch(line, -1)
			if len(re) == 1 {
				femail := re[0][1]
				fname := re[0][2]
				flogin := re[0][4]
				if femail == email && fname == name {
					// fmt.Printf("femail='%s', fname='%s' => login '%s'\n", femail, fname, flogin)
					login = flogin
					break
				}
			}
			buffer.Reset()
		}
	}
	if err == io.EOF {
		err = nil
	}
	if err != nil {
		panic(err)
	}
	fmt.Printf("cacheLogins='%s'\n", login)
	return login
}

var contributorRE = regexp.MustCompile(`.*?<a\s+href="[^"]+"\s+rel="contributor"\s*?>([^<]+)</a>`)

func scrapPage(SHA string) string {
	if SHA == "" {
		return ""
	}
	response, err := http.Get("https://github.com/git/git/commit/" + SHA)
	if err != nil {
		fmt.Printf("%s", err)
		os.Exit(1)
	} else {
		defer response.Body.Close()
		contents, err := ioutil.ReadAll(response.Body)
		if err != nil {
			fmt.Printf("%s", err)
			os.Exit(1)
		}
		lines := strings.Split(string(contents), `\n`)
		for _, line := range lines {
			if strings.Contains(line, "contributor") {
				r := contributorRE.FindAllStringSubmatch(line, -1)
				if len(r) > 0 {
					if len(r[0]) == 2 {
						// fmt.Printf("r[0]='%+v' => res='%s'\n", r[0], r[0][1])
						return r[0][1]
					}
				}
				fmt.Println("line='%+v'\n", r)
			}
		}
	}
	return ""
}
func addToCacheLogins(email string, name string, login string) string {
	fmt.Printf("addToCacheLogins '%s'\n", login)
	fi, err := os.OpenFile("logins.txt", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660)
	if err != nil {
		panic(err)
	}
	// close fi on exit and check for its returned error
	defer func() {
		if err := fi.Close(); err != nil {
			panic(err)
		}
	}()
	if _, err := fi.WriteString(fmt.Sprintf("%s#~#%s^()^%s\n", email, name, login)); err != nil {
		panic(err)
	}
	return login

}
<commit_msg>silent users login<commit_after>package users

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"seec2/internal/gh"
	"strings"

	"github.com/google/go-github/github"
)

type User struct {
	Login string
	Name  string
}

func AuthorNameAndLogin(c *gh.Commit) *User {
	clogin := login(*c.Author.Email, *c.Author.Name, *c.SHA)
	cname := *c.Author.Name
	return &User{Login: clogin, Name: cname}
}
func CommitterNameAndLogin(c *gh.Commit) *User {
	clogin := login(*c.Committer.Email, *c.Committer.Name, *c.SHA)
	cname := *c.Committer.Name
	return &User{Login: clogin, Name: cname}
}

func Login(email string, name string) string {
	return login(email, name, "")
}

func login(email string, name string, SHA string) string {
	// fmt.Printf("email='%s', name='%s'\n", email, name)
	if login := cacheLogins(email, name); login != "" {
		return login
	}
	if login := scrapPage(SHA); login != "" {
		return addToCacheLogins(email, name, login)
	}
	opts := &github.SearchOptions{Order: "desc"}
	var res *github.UsersSearchResult
	var err error
	if email != "" {
		res, _, err = gh.Client.Search.Users(email, opts)
		if err != nil {
			fmt.Printf("Unable to search user '%s': err '%v'", email, err)
			gh.GHex.Exit(1)
		}
	}
	nameNoDash := name
	if res == nil || *res.Total == 0 {
		nameNoDash = strings.Replace(name, "-", " ", -1)
		res, _, err = gh.Client.Search.Users(nameNoDash, opts)
		if err != nil {
			fmt.Printf("Unable to search user '%s': err '%v'", nameNoDash, err)
			gh.GHex.Exit(1)
		}
	}
	if res == nil || *res.Total == 0 {
		var resIssues *github.IssuesSearchResult
		issueSearch := fmt.Sprintf(`"Signed-off-by: %s <%s>"`, nameNoDash, email)
		resIssues, _, err = gh.Client.Search.Issues(issueSearch, opts)
		if err != nil {
			fmt.Printf("Unable to search issue '%s': err '%v'", issueSearch, err)
			gh.GHex.Exit(1)
		}
		if resIssues == nil || *resIssues.Total == 0 {
			return ""
		}
		issue := resIssues.Issues[0]
		return addToCacheLogins(email, name, *issue.User.Login)
	}
	if res == nil || *res.Total == 0 {
		return ""
	}
	return addToCacheLogins(email, name, *res.Users[0].Login)
}

var cacheLoginsRE = regexp.MustCompile(`^(.*)#~#(.*?)\^\((.*?)\)\^(.*?)$`)

func cacheLogins(email string, name string) string {
	login := ""
	fi, err := os.OpenFile("logins.txt", os.O_RDONLY|os.O_CREATE, 0660)
	if err != nil {
		panic(err)
	}
	// close fi on exit and check for its returned error
	defer func() {
		if err := fi.Close(); err != nil {
			panic(err)
		}
	}()
	var part []byte
	var prefix bool
	reader := bufio.NewReader(fi)
	buffer := bytes.NewBuffer(make([]byte, 0))
	for {
		if part, prefix, err = reader.ReadLine(); err != nil {
			break
		}
		buffer.Write(part)
		if !prefix {
			line := buffer.String()
			re := cacheLoginsRE.FindAllStringSubmatch(line, -1)
			if len(re) == 1 {
				femail := re[0][1]
				fname := re[0][2]
				flogin := re[0][4]
				if femail == email && fname == name {
					// fmt.Printf("femail='%s', fname='%s' => login '%s'\n", femail, fname, flogin)
					login = flogin
					break
				}
			}
			buffer.Reset()
		}
	}
	if err == io.EOF {
		err = nil
	}
	if err != nil {
		panic(err)
	}
	// fmt.Printf("cacheLogins='%s'\n", login)
	return login
}

var contributorRE = regexp.MustCompile(`.*?<a\s+href="[^"]+"\s+rel="contributor"\s*?>([^<]+)</a>`)

func scrapPage(SHA string) string {
	if SHA == "" {
		return ""
	}
	response, err := http.Get("https://github.com/git/git/commit/" + SHA)
	if err != nil {
		fmt.Printf("%s", err)
		os.Exit(1)
	} else {
		defer response.Body.Close()
		contents, err := ioutil.ReadAll(response.Body)
		if err != nil {
			fmt.Printf("%s", err)
			os.Exit(1)
		}
		lines := strings.Split(string(contents), `\n`)
		for _, line := range lines {
			if strings.Contains(line, "contributor") {
				r := contributorRE.FindAllStringSubmatch(line, -1)
				if len(r) > 0 {
					if len(r[0]) == 2 {
						// fmt.Printf("r[0]='%+v' => res='%s'\n", r[0], r[0][1])
						return r[0][1]
					}
				}
				fmt.Println("line='%+v'\n", r)
			}
		}
	}
	return ""
}
func addToCacheLogins(email string, name string, login string) string {
	fmt.Printf("addToCacheLogins '%s'\n", login)
	fi, err := os.OpenFile("logins.txt", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660)
	if err != nil {
		panic(err)
	}
	// close fi on exit and check for its returned error
	defer func() {
		if err := fi.Close(); err != nil {
			panic(err)
		}
	}()
	if _, err := fi.WriteString(fmt.Sprintf("%s#~#%s^()^%s\n", email, name, login)); err != nil {
		panic(err)
	}
	return login

}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Ceph-CSI 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 util

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"path"
	"strings"
)

const (
	mapperFilePrefix     = "luks-rbd-"
	mapperFilePathPrefix = "/dev/mapper"

	// kmsConfigPath is the location of the vault config file.
	kmsConfigPath = "/etc/ceph-csi-encryption-kms-config/config.json"

	// Passphrase size - 20 bytes is 160 bits to satisfy:
	// https://tools.ietf.org/html/rfc6749#section-10.10
	encryptionPassphraseSize = 20
)

var (
	// ErrDEKStoreNotFound is an error that is returned when the DEKStore
	// has not been configured for the volumeID in the KMS instance.
	ErrDEKStoreNotFound = errors.New("DEKStore not found")

	// ErrDEKStoreNeeded is an indication that gets returned with
	// NewVolumeEncryption when the KMS does not include support for the
	// DEKStore interface.
	ErrDEKStoreNeeded = errors.New("DEKStore required, use " +
		"VolumeEncryption.SetDEKStore()")
)

type VolumeEncryption struct {
	KMS EncryptionKMS

	// dekStore that will be used, this can be the EncryptionKMS or a
	// different object implementing the DEKStore interface.
	dekStore DEKStore

	id string
}

// NewVolumeEncryption creates a new instance of VolumeEncryption and
// configures the DEKStore. If the KMS does not provide a DEKStore interface,
// the VolumeEncryption will be created *and* a ErrDEKStoreNeeded is returned.
// Callers that receive a ErrDEKStoreNeeded error, should use
// VolumeEncryption.SetDEKStore() to configure an alternative storage for the
// DEKs.
func NewVolumeEncryption(id string, kms EncryptionKMS) (*VolumeEncryption, error) {
	kmsID := id
	if kmsID == "" {
		// if kmsID is not set, encryption is enabled, and the type is
		// SecretsKMS
		kmsID = defaultKMSType
	}

	ve := &VolumeEncryption{
		id:  kmsID,
		KMS: kms,
	}

	if kms.requiresDEKStore() == DEKStoreIntegrated {
		dekStore, ok := kms.(DEKStore)
		if !ok {
			return nil, fmt.Errorf("KMS %T does not implement the "+
				"DEKStore interface", kms)
		}

		ve.dekStore = dekStore

		return ve, nil
	}

	return ve, ErrDEKStoreNeeded
}

// SetDEKStore sets the DEKStore for this VolumeEncryption instance. It will be
// used when StoreNewCryptoPassphrase() or RemoveDEK() is called.
func (ve *VolumeEncryption) SetDEKStore(dekStore DEKStore) {
	ve.dekStore = dekStore
}

// Destroy frees any resources that the VolumeEncryption instance allocated.
func (ve *VolumeEncryption) Destroy() {
	ve.KMS.Destroy()
}

// RemoveDEK deletes the DEK for a particular volumeID from the DEKStore linked
// with this VolumeEncryption instance.
func (ve *VolumeEncryption) RemoveDEK(volumeID string) error {
	if ve.dekStore == nil {
		return ErrDEKStoreNotFound
	}

	return ve.dekStore.RemoveDEK(volumeID)
}

func (ve *VolumeEncryption) GetID() string {
	return ve.id
}

// EncryptionKMS provides external Key Management System for encryption
// passphrases storage.
type EncryptionKMS interface {
	Destroy()

	// requiresDEKStore returns the DEKStoreType that is needed to be
	// configure for the KMS. Nothing needs to be done when this function
	// returns DEKStoreIntegrated, otherwise you will need to configure an
	// alternative storage for the DEKs.
	requiresDEKStore() DEKStoreType

	// EncryptDEK provides a way for a KMS to encrypt a DEK. In case the
	// encryption is done transparently inside the KMS service, the
	// function can return an unencrypted value.
	EncryptDEK(volumeID, plainDEK string) (string, error)

	// DecryptDEK provides a way for a KMS to decrypt a DEK. In case the
	// encryption is done transparently inside the KMS service, the
	// function does not need to do anything except return the encyptedDEK
	// as it was received.
	DecryptDEK(volumeID, encyptedDEK string) (string, error)
}

// DEKStoreType describes what DEKStore needs to be configured when using a
// particular KMS. A KMS might support different DEKStores depending on its
// configuration.
type DEKStoreType string

const (
	// DEKStoreIntegrated indicates that the KMS itself supports storing
	// DEKs.
	DEKStoreIntegrated = DEKStoreType("")
	// DEKStoreMetadata indicates that the KMS should be configured to
	// store the DEK in the metadata of the volume.
	DEKStoreMetadata = DEKStoreType("metadata")
)

// DEKStore allows KMS instances to implement a modular backend for DEK
// storage. This can be used to store the DEK in a different location, in case
// the KMS can not store passphrases for volumes.
type DEKStore interface {
	// StoreDEK saves the DEK in the configured store.
	StoreDEK(volumeID string, dek string) error
	// FetchDEK reads the DEK from the configured store and returns it.
	FetchDEK(volumeID string) (string, error)
	// RemoveDEK deletes the DEK from the configured store.
	RemoveDEK(volumeID string) error
}

// integratedDEK is a DEKStore that can not be configured. Either the KMS does
// not use a DEK, or the DEK is stored in the KMS without additional
// configuration options.
type integratedDEK struct{}

func (i integratedDEK) requiresDEKStore() DEKStoreType {
	return DEKStoreIntegrated
}

func (i integratedDEK) EncryptDEK(volumeID, plainDEK string) (string, error) {
	return plainDEK, nil
}

func (i integratedDEK) DecryptDEK(volumeID, encyptedDEK string) (string, error) {
	return encyptedDEK, nil
}

// StoreCryptoPassphrase takes an unencrypted passphrase, encrypts it and saves
// it in the DEKStore.
func (ve *VolumeEncryption) StoreCryptoPassphrase(volumeID, passphrase string) error {
	encryptedPassphrase, err := ve.KMS.EncryptDEK(volumeID, passphrase)
	if err != nil {
		return fmt.Errorf("failed encrypt the passphrase for %s: %w", volumeID, err)
	}

	err = ve.dekStore.StoreDEK(volumeID, encryptedPassphrase)
	if err != nil {
		return fmt.Errorf("failed to save the passphrase for %s: %w", volumeID, err)
	}

	return nil
}

// StoreNewCryptoPassphrase generates a new passphrase and saves it in the KMS.
func (ve *VolumeEncryption) StoreNewCryptoPassphrase(volumeID string) error {
	passphrase, err := generateNewEncryptionPassphrase()
	if err != nil {
		return fmt.Errorf("failed to generate passphrase for %s: %w", volumeID, err)
	}

	return ve.StoreCryptoPassphrase(volumeID, passphrase)
}

// GetCryptoPassphrase Retrieves passphrase to encrypt volume.
func (ve *VolumeEncryption) GetCryptoPassphrase(volumeID string) (string, error) {
	passphrase, err := ve.dekStore.FetchDEK(volumeID)
	if err != nil {
		return "", err
	}

	return ve.KMS.DecryptDEK(volumeID, passphrase)
}

// generateNewEncryptionPassphrase generates a random passphrase for encryption.
func generateNewEncryptionPassphrase() (string, error) {
	bytesPassphrase := make([]byte, encryptionPassphraseSize)
	_, err := rand.Read(bytesPassphrase)
	if err != nil {
		return "", err
	}

	return base64.URLEncoding.EncodeToString(bytesPassphrase), nil
}

// VolumeMapper returns file name and it's path to where encrypted device should be open.
func VolumeMapper(volumeID string) (mapperFile, mapperFilePath string) {
	mapperFile = mapperFilePrefix + volumeID
	mapperFilePath = path.Join(mapperFilePathPrefix, mapperFile)

	return mapperFile, mapperFilePath
}

// EncryptVolume encrypts provided device with LUKS.
func EncryptVolume(ctx context.Context, devicePath, passphrase string) error {
	DebugLog(ctx, "Encrypting device %s with LUKS", devicePath)
	if _, _, err := LuksFormat(devicePath, passphrase); err != nil {
		return fmt.Errorf("failed to encrypt device %s with LUKS: %w", devicePath, err)
	}

	return nil
}

// OpenEncryptedVolume opens volume so that it can be used by the client.
func OpenEncryptedVolume(ctx context.Context, devicePath, mapperFile, passphrase string) error {
	DebugLog(ctx, "Opening device %s with LUKS on %s", devicePath, mapperFile)
	_, stderr, err := LuksOpen(devicePath, mapperFile, passphrase)
	if err != nil {
		WarningLog(ctx, "failed to open LUKS device %q: %s", devicePath, stderr)
	}

	return err
}

// CloseEncryptedVolume closes encrypted volume so it can be detached.
func CloseEncryptedVolume(ctx context.Context, mapperFile string) error {
	DebugLog(ctx, "Closing LUKS device %s", mapperFile)
	_, _, err := LuksClose(mapperFile)

	return err
}

// IsDeviceOpen determines if encrypted device is already open.
func IsDeviceOpen(ctx context.Context, device string) (bool, error) {
	_, mappedFile, err := DeviceEncryptionStatus(ctx, device)

	return (mappedFile != ""), err
}

// DeviceEncryptionStatus looks to identify if the passed device is a LUKS mapping
// and if so what the device is and the mapper name as used by LUKS.
// If not, just returns the original device and an empty string.
func DeviceEncryptionStatus(ctx context.Context, devicePath string) (mappedDevice, mapper string, err error) {
	if !strings.HasPrefix(devicePath, mapperFilePathPrefix) {
		return devicePath, "", nil
	}
	mapPath := strings.TrimPrefix(devicePath, mapperFilePathPrefix+"/")
	stdout, _, err := LuksStatus(mapPath)
	if err != nil {
		DebugLog(ctx, "device %s is not an active LUKS device: %v", devicePath, err)

		return devicePath, "", nil
	}
	lines := strings.Split(string(stdout), "\n")
	if len(lines) < 1 {
		return "", "", fmt.Errorf("device encryption status returned no stdout for %s", devicePath)
	}
	if !strings.HasSuffix(lines[0], " is active.") {
		// Implies this is not a LUKS device
		return devicePath, "", nil
	}
	for i := 1; i < len(lines); i++ {
		kv := strings.SplitN(strings.TrimSpace(lines[i]), ":", 2)
		if len(kv) < 1 {
			return "", "", fmt.Errorf("device encryption status output for %s is badly formatted: %s",
				devicePath, lines[i])
		}
		if strings.Compare(kv[0], "device") == 0 {
			return strings.TrimSpace(kv[1]), mapPath, nil
		}
	}
	// Identified as LUKS, but failed to identify a mapped device
	return "", "", fmt.Errorf("mapped device not found in path %s", devicePath)
}
<commit_msg>util: fix bug in DeviceEncryptionStatus()<commit_after>/*
Copyright 2019 The Ceph-CSI 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 util

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"path"
	"strings"
)

const (
	mapperFilePrefix     = "luks-rbd-"
	mapperFilePathPrefix = "/dev/mapper"

	// kmsConfigPath is the location of the vault config file.
	kmsConfigPath = "/etc/ceph-csi-encryption-kms-config/config.json"

	// Passphrase size - 20 bytes is 160 bits to satisfy:
	// https://tools.ietf.org/html/rfc6749#section-10.10
	encryptionPassphraseSize = 20
)

var (
	// ErrDEKStoreNotFound is an error that is returned when the DEKStore
	// has not been configured for the volumeID in the KMS instance.
	ErrDEKStoreNotFound = errors.New("DEKStore not found")

	// ErrDEKStoreNeeded is an indication that gets returned with
	// NewVolumeEncryption when the KMS does not include support for the
	// DEKStore interface.
	ErrDEKStoreNeeded = errors.New("DEKStore required, use " +
		"VolumeEncryption.SetDEKStore()")
)

type VolumeEncryption struct {
	KMS EncryptionKMS

	// dekStore that will be used, this can be the EncryptionKMS or a
	// different object implementing the DEKStore interface.
	dekStore DEKStore

	id string
}

// NewVolumeEncryption creates a new instance of VolumeEncryption and
// configures the DEKStore. If the KMS does not provide a DEKStore interface,
// the VolumeEncryption will be created *and* a ErrDEKStoreNeeded is returned.
// Callers that receive a ErrDEKStoreNeeded error, should use
// VolumeEncryption.SetDEKStore() to configure an alternative storage for the
// DEKs.
func NewVolumeEncryption(id string, kms EncryptionKMS) (*VolumeEncryption, error) {
	kmsID := id
	if kmsID == "" {
		// if kmsID is not set, encryption is enabled, and the type is
		// SecretsKMS
		kmsID = defaultKMSType
	}

	ve := &VolumeEncryption{
		id:  kmsID,
		KMS: kms,
	}

	if kms.requiresDEKStore() == DEKStoreIntegrated {
		dekStore, ok := kms.(DEKStore)
		if !ok {
			return nil, fmt.Errorf("KMS %T does not implement the "+
				"DEKStore interface", kms)
		}

		ve.dekStore = dekStore

		return ve, nil
	}

	return ve, ErrDEKStoreNeeded
}

// SetDEKStore sets the DEKStore for this VolumeEncryption instance. It will be
// used when StoreNewCryptoPassphrase() or RemoveDEK() is called.
func (ve *VolumeEncryption) SetDEKStore(dekStore DEKStore) {
	ve.dekStore = dekStore
}

// Destroy frees any resources that the VolumeEncryption instance allocated.
func (ve *VolumeEncryption) Destroy() {
	ve.KMS.Destroy()
}

// RemoveDEK deletes the DEK for a particular volumeID from the DEKStore linked
// with this VolumeEncryption instance.
func (ve *VolumeEncryption) RemoveDEK(volumeID string) error {
	if ve.dekStore == nil {
		return ErrDEKStoreNotFound
	}

	return ve.dekStore.RemoveDEK(volumeID)
}

func (ve *VolumeEncryption) GetID() string {
	return ve.id
}

// EncryptionKMS provides external Key Management System for encryption
// passphrases storage.
type EncryptionKMS interface {
	Destroy()

	// requiresDEKStore returns the DEKStoreType that is needed to be
	// configure for the KMS. Nothing needs to be done when this function
	// returns DEKStoreIntegrated, otherwise you will need to configure an
	// alternative storage for the DEKs.
	requiresDEKStore() DEKStoreType

	// EncryptDEK provides a way for a KMS to encrypt a DEK. In case the
	// encryption is done transparently inside the KMS service, the
	// function can return an unencrypted value.
	EncryptDEK(volumeID, plainDEK string) (string, error)

	// DecryptDEK provides a way for a KMS to decrypt a DEK. In case the
	// encryption is done transparently inside the KMS service, the
	// function does not need to do anything except return the encyptedDEK
	// as it was received.
	DecryptDEK(volumeID, encyptedDEK string) (string, error)
}

// DEKStoreType describes what DEKStore needs to be configured when using a
// particular KMS. A KMS might support different DEKStores depending on its
// configuration.
type DEKStoreType string

const (
	// DEKStoreIntegrated indicates that the KMS itself supports storing
	// DEKs.
	DEKStoreIntegrated = DEKStoreType("")
	// DEKStoreMetadata indicates that the KMS should be configured to
	// store the DEK in the metadata of the volume.
	DEKStoreMetadata = DEKStoreType("metadata")
)

// DEKStore allows KMS instances to implement a modular backend for DEK
// storage. This can be used to store the DEK in a different location, in case
// the KMS can not store passphrases for volumes.
type DEKStore interface {
	// StoreDEK saves the DEK in the configured store.
	StoreDEK(volumeID string, dek string) error
	// FetchDEK reads the DEK from the configured store and returns it.
	FetchDEK(volumeID string) (string, error)
	// RemoveDEK deletes the DEK from the configured store.
	RemoveDEK(volumeID string) error
}

// integratedDEK is a DEKStore that can not be configured. Either the KMS does
// not use a DEK, or the DEK is stored in the KMS without additional
// configuration options.
type integratedDEK struct{}

func (i integratedDEK) requiresDEKStore() DEKStoreType {
	return DEKStoreIntegrated
}

func (i integratedDEK) EncryptDEK(volumeID, plainDEK string) (string, error) {
	return plainDEK, nil
}

func (i integratedDEK) DecryptDEK(volumeID, encyptedDEK string) (string, error) {
	return encyptedDEK, nil
}

// StoreCryptoPassphrase takes an unencrypted passphrase, encrypts it and saves
// it in the DEKStore.
func (ve *VolumeEncryption) StoreCryptoPassphrase(volumeID, passphrase string) error {
	encryptedPassphrase, err := ve.KMS.EncryptDEK(volumeID, passphrase)
	if err != nil {
		return fmt.Errorf("failed encrypt the passphrase for %s: %w", volumeID, err)
	}

	err = ve.dekStore.StoreDEK(volumeID, encryptedPassphrase)
	if err != nil {
		return fmt.Errorf("failed to save the passphrase for %s: %w", volumeID, err)
	}

	return nil
}

// StoreNewCryptoPassphrase generates a new passphrase and saves it in the KMS.
func (ve *VolumeEncryption) StoreNewCryptoPassphrase(volumeID string) error {
	passphrase, err := generateNewEncryptionPassphrase()
	if err != nil {
		return fmt.Errorf("failed to generate passphrase for %s: %w", volumeID, err)
	}

	return ve.StoreCryptoPassphrase(volumeID, passphrase)
}

// GetCryptoPassphrase Retrieves passphrase to encrypt volume.
func (ve *VolumeEncryption) GetCryptoPassphrase(volumeID string) (string, error) {
	passphrase, err := ve.dekStore.FetchDEK(volumeID)
	if err != nil {
		return "", err
	}

	return ve.KMS.DecryptDEK(volumeID, passphrase)
}

// generateNewEncryptionPassphrase generates a random passphrase for encryption.
func generateNewEncryptionPassphrase() (string, error) {
	bytesPassphrase := make([]byte, encryptionPassphraseSize)
	_, err := rand.Read(bytesPassphrase)
	if err != nil {
		return "", err
	}

	return base64.URLEncoding.EncodeToString(bytesPassphrase), nil
}

// VolumeMapper returns file name and it's path to where encrypted device should be open.
func VolumeMapper(volumeID string) (mapperFile, mapperFilePath string) {
	mapperFile = mapperFilePrefix + volumeID
	mapperFilePath = path.Join(mapperFilePathPrefix, mapperFile)

	return mapperFile, mapperFilePath
}

// EncryptVolume encrypts provided device with LUKS.
func EncryptVolume(ctx context.Context, devicePath, passphrase string) error {
	DebugLog(ctx, "Encrypting device %s with LUKS", devicePath)
	if _, _, err := LuksFormat(devicePath, passphrase); err != nil {
		return fmt.Errorf("failed to encrypt device %s with LUKS: %w", devicePath, err)
	}

	return nil
}

// OpenEncryptedVolume opens volume so that it can be used by the client.
func OpenEncryptedVolume(ctx context.Context, devicePath, mapperFile, passphrase string) error {
	DebugLog(ctx, "Opening device %s with LUKS on %s", devicePath, mapperFile)
	_, stderr, err := LuksOpen(devicePath, mapperFile, passphrase)
	if err != nil {
		WarningLog(ctx, "failed to open LUKS device %q: %s", devicePath, stderr)
	}

	return err
}

// CloseEncryptedVolume closes encrypted volume so it can be detached.
func CloseEncryptedVolume(ctx context.Context, mapperFile string) error {
	DebugLog(ctx, "Closing LUKS device %s", mapperFile)
	_, _, err := LuksClose(mapperFile)

	return err
}

// IsDeviceOpen determines if encrypted device is already open.
func IsDeviceOpen(ctx context.Context, device string) (bool, error) {
	_, mappedFile, err := DeviceEncryptionStatus(ctx, device)

	return (mappedFile != ""), err
}

// DeviceEncryptionStatus looks to identify if the passed device is a LUKS mapping
// and if so what the device is and the mapper name as used by LUKS.
// If not, just returns the original device and an empty string.
func DeviceEncryptionStatus(ctx context.Context, devicePath string) (mappedDevice, mapper string, err error) {
	if !strings.HasPrefix(devicePath, mapperFilePathPrefix) {
		return devicePath, "", nil
	}
	mapPath := strings.TrimPrefix(devicePath, mapperFilePathPrefix+"/")
	stdout, _, err := LuksStatus(mapPath)
	if err != nil {
		DebugLog(ctx, "device %s is not an active LUKS device: %v", devicePath, err)

		return devicePath, "", nil
	}
	lines := strings.Split(string(stdout), "\n")
	if len(lines) < 1 {
		return "", "", fmt.Errorf("device encryption status returned no stdout for %s", devicePath)
	}
	// The line will look like: "/dev/mapper/xxx is active and is in use."
	if !strings.Contains(lines[0], " is active") {
		// Implies this is not a LUKS device
		return devicePath, "", nil
	}
	for i := 1; i < len(lines); i++ {
		kv := strings.SplitN(strings.TrimSpace(lines[i]), ":", 2)
		if len(kv) < 1 {
			return "", "", fmt.Errorf("device encryption status output for %s is badly formatted: %s",
				devicePath, lines[i])
		}
		if strings.Compare(kv[0], "device") == 0 {
			return strings.TrimSpace(kv[1]), mapPath, nil
		}
	}
	// Identified as LUKS, but failed to identify a mapped device
	return "", "", fmt.Errorf("mapped device not found in path %s", devicePath)
}
<|endoftext|>


<commit_before>package web

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/mdlayher/mdlayher.com/internal/github"
	"github.com/mdlayher/mdlayher.com/internal/httptalks"
	"github.com/mdlayher/mdlayher.com/internal/medium"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// A handler is a http.Handler that serves content using a template.
type handler struct {
	static   StaticContent
	redirect http.Handler
	ghc      github.Client
	mc       medium.Client
	htc      httptalks.Client

	requestDurationSeconds *prometheus.HistogramVec
}

// NewHandler creates a http.Handler that serves content using a template.
// Additional dynamic content can be added by providing non-nil clients for
// various services.
func NewHandler(static StaticContent, ghc github.Client, mc medium.Client, htc httptalks.Client) http.Handler {
	const namespace = "mdlayher"

	h := &handler{
		static:   static,
		redirect: NewRedirectHandler(static.Domain),
		ghc:      ghc,
		mc:       mc,
		htc:      htc,

		requestDurationSeconds: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: namespace,
			Name:      "request_duration_seconds",
			Help:      "Duration of requests to external services.",
			Buckets:   prometheus.ExponentialBuckets(0.1, 2, 7),
		}, []string{"target"}),
	}

	prometheus.MustRegister(h.requestDurationSeconds)

	// Set up application routes and metrics.
	mux := http.NewServeMux()
	mux.Handle("/", prometheus.InstrumentHandler("web", h))
	mux.Handle("/metrics", promhttp.Handler())

	return mux
}

// ServeHTTP implements http.Handler.
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Redirect any TLS subdomain requests to the base domain name.
	if r.TLS != nil && strings.Count(r.TLS.ServerName, ".") > 1 {
		h.redirect.ServeHTTP(w, r)
		return
	}

	// HSTS support: https://hstspreload.org/.
	w.Header().Set("Strict-Transport-Security", HSTSHeader(time.Now()))

	// Dispatch a group of futures to fetch external data, to be evaluated
	// once ready to populate the Content structure.
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	repoFn := h.fetchGitHub(ctx)
	postFn := h.fetchMedium(ctx)
	talkFn := h.fetchTalks(ctx)

	repos, err := repoFn()
	if err != nil {
		httpError(w, "failed to fetch github repositories: %v", err)
		return
	}

	posts, err := postFn()
	if err != nil {
		httpError(w, "failed to fetch medium posts: %v", err)
		return
	}

	talks, err := talkFn()
	if err != nil {
		httpError(w, "failed to fetch talks: %v", err)
		return
	}

	// Build content for display.
	content := Content{
		Static: h.static,
		GitHub: GitHubContent{
			Repositories: repos,
		},
		Medium: MediumContent{
			Posts: posts,
		},
		HTTPTalks: HTTPTalksContent{
			Talks: talks,
		},
	}

	if err := tmpl.Execute(w, content); err != nil {
		httpError(w, "failed to execute template: %v", err)
		return
	}
}

// NewRedirectHandler creates a http.Handler that redirects clients to the
// specified domain using TLS and no subdomain.
func NewRedirectHandler(domain string) http.Handler {
	return http.RedirectHandler(fmt.Sprintf("https://%s", domain), http.StatusMovedPermanently)
}

// httpError returns a generic HTTP 500 to a client and logs an informative
// message to the logger.
func httpError(w http.ResponseWriter, format string, a ...interface{}) {
	log.Printf(format, a...)
	http.Error(w, "internal server error", http.StatusInternalServerError)
}
<commit_msg>internal/web: fix metrics registration for tests<commit_after>package web

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"

	"github.com/mdlayher/mdlayher.com/internal/github"
	"github.com/mdlayher/mdlayher.com/internal/httptalks"
	"github.com/mdlayher/mdlayher.com/internal/medium"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// A handler is a http.Handler that serves content using a template.
type handler struct {
	static   StaticContent
	redirect http.Handler
	ghc      github.Client
	mc       medium.Client
	htc      httptalks.Client

	requestDurationSeconds *prometheus.HistogramVec
}

// NewHandler creates a http.Handler that serves content using a template.
// Additional dynamic content can be added by providing non-nil clients for
// various services.
func NewHandler(static StaticContent, ghc github.Client, mc medium.Client, htc httptalks.Client) http.Handler {
	const namespace = "mdlayher"

	h := &handler{
		static:   static,
		redirect: NewRedirectHandler(static.Domain),
		ghc:      ghc,
		mc:       mc,
		htc:      htc,

		requestDurationSeconds: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: namespace,
			Name:      "request_duration_seconds",
			Help:      "Duration of requests to external services.",
			Buckets:   prometheus.ExponentialBuckets(0.1, 2, 7),
		}, []string{"target"}),
	}

	// Don't worry about registering this multiple times during tests.
	_ = prometheus.Register(h.requestDurationSeconds)

	// Set up application routes and metrics.
	mux := http.NewServeMux()
	mux.Handle("/", prometheus.InstrumentHandler("web", h))
	mux.Handle("/metrics", promhttp.Handler())

	return mux
}

// ServeHTTP implements http.Handler.
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Redirect any TLS subdomain requests to the base domain name.
	if r.TLS != nil && strings.Count(r.TLS.ServerName, ".") > 1 {
		h.redirect.ServeHTTP(w, r)
		return
	}

	// HSTS support: https://hstspreload.org/.
	w.Header().Set("Strict-Transport-Security", HSTSHeader(time.Now()))

	// Dispatch a group of futures to fetch external data, to be evaluated
	// once ready to populate the Content structure.
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	repoFn := h.fetchGitHub(ctx)
	postFn := h.fetchMedium(ctx)
	talkFn := h.fetchTalks(ctx)

	repos, err := repoFn()
	if err != nil {
		httpError(w, "failed to fetch github repositories: %v", err)
		return
	}

	posts, err := postFn()
	if err != nil {
		httpError(w, "failed to fetch medium posts: %v", err)
		return
	}

	talks, err := talkFn()
	if err != nil {
		httpError(w, "failed to fetch talks: %v", err)
		return
	}

	// Build content for display.
	content := Content{
		Static: h.static,
		GitHub: GitHubContent{
			Repositories: repos,
		},
		Medium: MediumContent{
			Posts: posts,
		},
		HTTPTalks: HTTPTalksContent{
			Talks: talks,
		},
	}

	if err := tmpl.Execute(w, content); err != nil {
		httpError(w, "failed to execute template: %v", err)
		return
	}
}

// NewRedirectHandler creates a http.Handler that redirects clients to the
// specified domain using TLS and no subdomain.
func NewRedirectHandler(domain string) http.Handler {
	return http.RedirectHandler(fmt.Sprintf("https://%s", domain), http.StatusMovedPermanently)
}

// httpError returns a generic HTTP 500 to a client and logs an informative
// message to the logger.
func httpError(w http.ResponseWriter, format string, a ...interface{}) {
	log.Printf(format, a...)
	http.Error(w, "internal server error", http.StatusInternalServerError)
}
<|endoftext|>


<commit_before>// ----------------------------------------------------------------------------
//
//     This file is copied here by Magic Modules. The code for building up a
//     storage bucket object is copied from the manually implemented
//     provider file:
//     third_party/terraform/resources/resource_storage_bucket.go
//
// ----------------------------------------------------------------------------
package google

import (
	"fmt"

	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"

	"google.golang.org/api/storage/v1"
)

func GetStorageBucketCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := assetName(d, config, "//storage.googleapis.com/{{name}}")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetStorageBucketApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "storage.googleapis.com/Bucket",
			Resource: &AssetResource{
				Version:              "v1",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/storage/v1/rest",
				DiscoveryName:        "Bucket",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetStorageBucketApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	project, err := getProject(d, config)
	if err != nil {
		return nil, err
	}

	// Get the bucket and location
	bucket := d.Get("name").(string)
	location := d.Get("location").(string)

	// Create a bucket, setting the labels, location and name.
	sb := &storage.Bucket{
		Name:             bucket,
		Labels:           expandLabels(d),
		Location:         location,
		IamConfiguration: expandIamConfiguration(d),
	}

	if v, ok := d.GetOk("storage_class"); ok {
		sb.StorageClass = v.(string)
	}

	if err := resourceGCSBucketLifecycleCreateOrUpdate(d, sb); err != nil {
		return nil, err
	}

	if v, ok := d.GetOk("versioning"); ok {
		sb.Versioning = expandBucketVersioning(v)
	}

	if v, ok := d.GetOk("website"); ok {
		sb.Website = expandBucketWebsite(v.([]interface{}))
	}

	if v, ok := d.GetOk("retention_policy"); ok {
		retention_policies := v.([]interface{})

		sb.RetentionPolicy = &storage.BucketRetentionPolicy{}

		retentionPolicy := retention_policies[0].(map[string]interface{})

		if v, ok := retentionPolicy["retention_period"]; ok {
			sb.RetentionPolicy.RetentionPeriod = int64(v.(int))
		}
	}

	if v, ok := d.GetOk("cors"); ok {
		sb.Cors = expandCors(v.([]interface{}))
	}

	if v, ok := d.GetOk("logging"); ok {
		sb.Logging = expandBucketLogging(v.([]interface{}))
	}

	if v, ok := d.GetOk("encryption"); ok {
		sb.Encryption = expandBucketEncryption(v.([]interface{}))
	}

	if v, ok := d.GetOk("requester_pays"); ok {
		sb.Billing = &storage.BucketBilling{
			RequesterPays: v.(bool),
		}
	}

	m, err := jsonMap(sb)
	if err != nil {
		return nil, err
	}
	m["project"] = project

	return m, nil
}

func expandCors(configured []interface{}) []*storage.BucketCors {
	corsRules := make([]*storage.BucketCors, 0, len(configured))
	for _, raw := range configured {
		data := raw.(map[string]interface{})
		corsRule := storage.BucketCors{
			Origin:         convertStringArr(data["origin"].([]interface{})),
			Method:         convertStringArr(data["method"].([]interface{})),
			ResponseHeader: convertStringArr(data["response_header"].([]interface{})),
			MaxAgeSeconds:  int64(data["max_age_seconds"].(int)),
		}

		corsRules = append(corsRules, &corsRule)
	}
	return corsRules
}

func expandBucketEncryption(configured interface{}) *storage.BucketEncryption {
	encs := configured.([]interface{})
	if len(encs) == 0 || encs[0] == nil {
		return nil
	}
	enc := encs[0].(map[string]interface{})
	keyname := enc["default_kms_key_name"]
	if keyname == nil || keyname.(string) == "" {
		return nil
	}
	bucketenc := &storage.BucketEncryption{
		DefaultKmsKeyName: keyname.(string),
	}
	return bucketenc
}

func expandBucketLogging(configured interface{}) *storage.BucketLogging {
	loggings := configured.([]interface{})
	if len(loggings) == 0 {
		return nil
	}

	logging := loggings[0].(map[string]interface{})

	bucketLogging := &storage.BucketLogging{
		LogBucket:       logging["log_bucket"].(string),
		LogObjectPrefix: logging["log_object_prefix"].(string),
	}

	return bucketLogging
}

func expandBucketVersioning(configured interface{}) *storage.BucketVersioning {
	versionings := configured.([]interface{})
	if len(versionings) == 0 {
		return nil
	}

	versioning := versionings[0].(map[string]interface{})

	bucketVersioning := &storage.BucketVersioning{}

	bucketVersioning.Enabled = versioning["enabled"].(bool)
	bucketVersioning.ForceSendFields = append(bucketVersioning.ForceSendFields, "Enabled")

	return bucketVersioning
}

func expandBucketWebsite(v interface{}) *storage.BucketWebsite {
	if v == nil {
		return nil
	}
	vs := v.([]interface{})

	if len(vs) < 1 || vs[0] == nil {
		return nil
	}

	website := vs[0].(map[string]interface{})
	w := &storage.BucketWebsite{}

	if v := website["not_found_page"]; v != "" {
		w.NotFoundPage = v.(string)
	}

	if v := website["main_page_suffix"]; v != "" {
		w.MainPageSuffix = v.(string)
	}

	return w
}

func resourceGCSBucketLifecycleCreateOrUpdate(d TerraformResourceData, sb *storage.Bucket) error {
	if v, ok := d.GetOk("lifecycle_rule"); ok {
		lifecycle_rules := v.([]interface{})

		sb.Lifecycle = &storage.BucketLifecycle{}
		sb.Lifecycle.Rule = make([]*storage.BucketLifecycleRule, 0, len(lifecycle_rules))

		for _, raw_lifecycle_rule := range lifecycle_rules {
			lifecycle_rule := raw_lifecycle_rule.(map[string]interface{})

			target_lifecycle_rule := &storage.BucketLifecycleRule{}

			if v, ok := lifecycle_rule["action"]; ok {
				if actions := v.(*schema.Set).List(); len(actions) == 1 {
					action := actions[0].(map[string]interface{})

					target_lifecycle_rule.Action = &storage.BucketLifecycleRuleAction{}

					if v, ok := action["type"]; ok {
						target_lifecycle_rule.Action.Type = v.(string)
					}

					if v, ok := action["storage_class"]; ok {
						target_lifecycle_rule.Action.StorageClass = v.(string)
					}
				} else {
					return fmt.Errorf("Exactly one action is required")
				}
			}

			if v, ok := lifecycle_rule["condition"]; ok {
				if conditions := v.(*schema.Set).List(); len(conditions) == 1 {
					condition := conditions[0].(map[string]interface{})

					target_lifecycle_rule.Condition = &storage.BucketLifecycleRuleCondition{}

					if v, ok := condition["age"]; ok {
						target_lifecycle_rule.Condition.Age = int64(v.(int))
					}

					if v, ok := condition["created_before"]; ok {
						target_lifecycle_rule.Condition.CreatedBefore = v.(string)
					}

					if v, ok := condition["matches_storage_class"]; ok {
						matches_storage_classes := v.([]interface{})

						target_matches_storage_classes := make([]string, 0, len(matches_storage_classes))

						for _, v := range matches_storage_classes {
							target_matches_storage_classes = append(target_matches_storage_classes, v.(string))
						}

						target_lifecycle_rule.Condition.MatchesStorageClass = target_matches_storage_classes
					}

					if v, ok := condition["num_newer_versions"]; ok {
						target_lifecycle_rule.Condition.NumNewerVersions = int64(v.(int))
					}
				} else {
					return fmt.Errorf("Exactly one condition is required")
				}
			}

			sb.Lifecycle.Rule = append(sb.Lifecycle.Rule, target_lifecycle_rule)
		}
	} else {
		sb.Lifecycle = &storage.BucketLifecycle{
			ForceSendFields: []string{"Rule"},
		}
	}

	return nil
}

func expandIamConfiguration(d TerraformResourceData) *storage.BucketIamConfiguration {
	return &storage.BucketIamConfiguration{
		ForceSendFields: []string{"BucketPolicyOnly"},
		BucketPolicyOnly: &storage.BucketIamConfigurationBucketPolicyOnly{
			Enabled:         d.Get("bucket_policy_only").(bool),
			ForceSendFields: []string{"Enabled"},
		},
	}
}
<commit_msg>add defaultEventBasedHold to storage buckets (#319)<commit_after>// ----------------------------------------------------------------------------
//
//     This file is copied here by Magic Modules. The code for building up a
//     storage bucket object is copied from the manually implemented
//     provider file:
//     third_party/terraform/resources/resource_storage_bucket.go
//
// ----------------------------------------------------------------------------
package google

import (
	"fmt"

	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"

	"google.golang.org/api/storage/v1"
)

func GetStorageBucketCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := assetName(d, config, "//storage.googleapis.com/{{name}}")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetStorageBucketApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "storage.googleapis.com/Bucket",
			Resource: &AssetResource{
				Version:              "v1",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/storage/v1/rest",
				DiscoveryName:        "Bucket",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetStorageBucketApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	project, err := getProject(d, config)
	if err != nil {
		return nil, err
	}

	// Get the bucket and location
	bucket := d.Get("name").(string)
	location := d.Get("location").(string)

	// Create a bucket, setting the labels, location and name.
	sb := &storage.Bucket{
		Name:             bucket,
		Labels:           expandLabels(d),
		Location:         location,
		IamConfiguration: expandIamConfiguration(d),
	}

	if v, ok := d.GetOk("storage_class"); ok {
		sb.StorageClass = v.(string)
	}

	if err := resourceGCSBucketLifecycleCreateOrUpdate(d, sb); err != nil {
		return nil, err
	}

	if v, ok := d.GetOk("versioning"); ok {
		sb.Versioning = expandBucketVersioning(v)
	}

	if v, ok := d.GetOk("website"); ok {
		sb.Website = expandBucketWebsite(v.([]interface{}))
	}

	if v, ok := d.GetOk("retention_policy"); ok {
		retention_policies := v.([]interface{})

		sb.RetentionPolicy = &storage.BucketRetentionPolicy{}

		retentionPolicy := retention_policies[0].(map[string]interface{})

		if v, ok := retentionPolicy["retention_period"]; ok {
			sb.RetentionPolicy.RetentionPeriod = int64(v.(int))
		}
	}

	if v, ok := d.GetOk("cors"); ok {
		sb.Cors = expandCors(v.([]interface{}))
	}

	if v, ok := d.GetOk("default_event_based_hold"); ok {
		sb.DefaultEventBasedHold = v.(bool)
	}

	if v, ok := d.GetOk("logging"); ok {
		sb.Logging = expandBucketLogging(v.([]interface{}))
	}

	if v, ok := d.GetOk("encryption"); ok {
		sb.Encryption = expandBucketEncryption(v.([]interface{}))
	}

	if v, ok := d.GetOk("requester_pays"); ok {
		sb.Billing = &storage.BucketBilling{
			RequesterPays: v.(bool),
		}
	}

	m, err := jsonMap(sb)
	if err != nil {
		return nil, err
	}
	m["project"] = project

	return m, nil
}

func expandCors(configured []interface{}) []*storage.BucketCors {
	corsRules := make([]*storage.BucketCors, 0, len(configured))
	for _, raw := range configured {
		data := raw.(map[string]interface{})
		corsRule := storage.BucketCors{
			Origin:         convertStringArr(data["origin"].([]interface{})),
			Method:         convertStringArr(data["method"].([]interface{})),
			ResponseHeader: convertStringArr(data["response_header"].([]interface{})),
			MaxAgeSeconds:  int64(data["max_age_seconds"].(int)),
		}

		corsRules = append(corsRules, &corsRule)
	}
	return corsRules
}

func expandBucketEncryption(configured interface{}) *storage.BucketEncryption {
	encs := configured.([]interface{})
	if len(encs) == 0 || encs[0] == nil {
		return nil
	}
	enc := encs[0].(map[string]interface{})
	keyname := enc["default_kms_key_name"]
	if keyname == nil || keyname.(string) == "" {
		return nil
	}
	bucketenc := &storage.BucketEncryption{
		DefaultKmsKeyName: keyname.(string),
	}
	return bucketenc
}

func expandBucketLogging(configured interface{}) *storage.BucketLogging {
	loggings := configured.([]interface{})
	if len(loggings) == 0 {
		return nil
	}

	logging := loggings[0].(map[string]interface{})

	bucketLogging := &storage.BucketLogging{
		LogBucket:       logging["log_bucket"].(string),
		LogObjectPrefix: logging["log_object_prefix"].(string),
	}

	return bucketLogging
}

func expandBucketVersioning(configured interface{}) *storage.BucketVersioning {
	versionings := configured.([]interface{})
	if len(versionings) == 0 {
		return nil
	}

	versioning := versionings[0].(map[string]interface{})

	bucketVersioning := &storage.BucketVersioning{}

	bucketVersioning.Enabled = versioning["enabled"].(bool)
	bucketVersioning.ForceSendFields = append(bucketVersioning.ForceSendFields, "Enabled")

	return bucketVersioning
}

func expandBucketWebsite(v interface{}) *storage.BucketWebsite {
	if v == nil {
		return nil
	}
	vs := v.([]interface{})

	if len(vs) < 1 || vs[0] == nil {
		return nil
	}

	website := vs[0].(map[string]interface{})
	w := &storage.BucketWebsite{}

	if v := website["not_found_page"]; v != "" {
		w.NotFoundPage = v.(string)
	}

	if v := website["main_page_suffix"]; v != "" {
		w.MainPageSuffix = v.(string)
	}

	return w
}

func resourceGCSBucketLifecycleCreateOrUpdate(d TerraformResourceData, sb *storage.Bucket) error {
	if v, ok := d.GetOk("lifecycle_rule"); ok {
		lifecycle_rules := v.([]interface{})

		sb.Lifecycle = &storage.BucketLifecycle{}
		sb.Lifecycle.Rule = make([]*storage.BucketLifecycleRule, 0, len(lifecycle_rules))

		for _, raw_lifecycle_rule := range lifecycle_rules {
			lifecycle_rule := raw_lifecycle_rule.(map[string]interface{})

			target_lifecycle_rule := &storage.BucketLifecycleRule{}

			if v, ok := lifecycle_rule["action"]; ok {
				if actions := v.(*schema.Set).List(); len(actions) == 1 {
					action := actions[0].(map[string]interface{})

					target_lifecycle_rule.Action = &storage.BucketLifecycleRuleAction{}

					if v, ok := action["type"]; ok {
						target_lifecycle_rule.Action.Type = v.(string)
					}

					if v, ok := action["storage_class"]; ok {
						target_lifecycle_rule.Action.StorageClass = v.(string)
					}
				} else {
					return fmt.Errorf("Exactly one action is required")
				}
			}

			if v, ok := lifecycle_rule["condition"]; ok {
				if conditions := v.(*schema.Set).List(); len(conditions) == 1 {
					condition := conditions[0].(map[string]interface{})

					target_lifecycle_rule.Condition = &storage.BucketLifecycleRuleCondition{}

					if v, ok := condition["age"]; ok {
						target_lifecycle_rule.Condition.Age = int64(v.(int))
					}

					if v, ok := condition["created_before"]; ok {
						target_lifecycle_rule.Condition.CreatedBefore = v.(string)
					}

					if v, ok := condition["matches_storage_class"]; ok {
						matches_storage_classes := v.([]interface{})

						target_matches_storage_classes := make([]string, 0, len(matches_storage_classes))

						for _, v := range matches_storage_classes {
							target_matches_storage_classes = append(target_matches_storage_classes, v.(string))
						}

						target_lifecycle_rule.Condition.MatchesStorageClass = target_matches_storage_classes
					}

					if v, ok := condition["num_newer_versions"]; ok {
						target_lifecycle_rule.Condition.NumNewerVersions = int64(v.(int))
					}
				} else {
					return fmt.Errorf("Exactly one condition is required")
				}
			}

			sb.Lifecycle.Rule = append(sb.Lifecycle.Rule, target_lifecycle_rule)
		}
	} else {
		sb.Lifecycle = &storage.BucketLifecycle{
			ForceSendFields: []string{"Rule"},
		}
	}

	return nil
}

func expandIamConfiguration(d TerraformResourceData) *storage.BucketIamConfiguration {
	return &storage.BucketIamConfiguration{
		ForceSendFields: []string{"BucketPolicyOnly"},
		BucketPolicyOnly: &storage.BucketIamConfigurationBucketPolicyOnly{
			Enabled:         d.Get("bucket_policy_only").(bool),
			ForceSendFields: []string{"Enabled"},
		},
	}
}
<|endoftext|>