diff --git a/cli/internal/state/BUILD.bazel b/cli/internal/state/BUILD.bazel index 914e81bd2..5d5e62607 100644 --- a/cli/internal/state/BUILD.bazel +++ b/cli/internal/state/BUILD.bazel @@ -3,12 +3,16 @@ load("//bazel/go:go_test.bzl", "go_test") go_library( name = "state", - srcs = ["state.go"], + srcs = [ + "state.go", + "state_doc.go", + ], importpath = "github.com/edgelesssys/constellation/v2/cli/internal/state", visibility = ["//cli:__subpackages__"], deps = [ "//internal/file", "@cat_dario_mergo//:mergo", + "@com_github_siderolabs_talos_pkg_machinery//config/encoder", ], ) @@ -19,8 +23,8 @@ go_test( deps = [ "//internal/constants", "//internal/file", + "@com_github_siderolabs_talos_pkg_machinery//config/encoder", "@com_github_spf13_afero//:afero", "@com_github_stretchr_testify//assert", - "@in_gopkg_yaml_v3//:yaml_v3", ], ) diff --git a/cli/internal/state/state.go b/cli/internal/state/state.go index 4ea873fc3..e1283d4a2 100644 --- a/cli/internal/state/state.go +++ b/cli/internal/state/state.go @@ -4,6 +4,11 @@ Copyright (c) Edgeless Systems GmbH SPDX-License-Identifier: AGPL-3.0-only */ +// This binary can be build from siderolabs/talos projects. Located at: +// https://github.com/siderolabs/talos/tree/master/hack/docgen +// +//go:generate docgen ./state.go ./state_doc.go Configuration + // package state defines the structure of the Constellation state file. package state @@ -30,9 +35,98 @@ func ReadFromFile(fileHandler file.Handler, path string) (*State, error) { // State describe the entire state to describe a Constellation cluster. type State struct { - Version string `yaml:"version"` + // description: | + // Schema version of this state file. + Version string `yaml:"version"` + + // TODO(msanft): Add link to self-managed infrastructure docs once existing. + + // description: | + // State of the cluster's cloud resources. These values are retrieved during + // cluster creation. In the case of self-managed infrastructure, the marked + // fields in this struct should be filled by the user as per + // https://docs.edgeless.systems/constellation/workflows/create. Infrastructure Infrastructure `yaml:"infrastructure"` - ClusterValues ClusterValues `yaml:"clusterValues"` + // description: | + // DO NOT EDIT. State of the Constellation Kubernetes cluster. + // These values are set during cluster initialization and should not be changed. + ClusterValues ClusterValues `yaml:"clusterValues"` +} + +// ClusterValues describe the (Kubernetes) cluster state, set during initialization of the cluster. +type ClusterValues struct { + // description: | + // Unique identifier of the cluster. + ClusterID string `yaml:"clusterID"` + // description: | + // Unique identifier of the owner of the cluster. + OwnerID string `yaml:"ownerID"` + // description: | + // Salt used to generate the ClusterID on the bootstrapping node. + MeasurementSalt []byte `yaml:"measurementSalt"` +} + +// Infrastructure describe the state related to the cloud resources of the cluster. +type Infrastructure struct { + // description: | + // Unique identifier the cluster's cloud resources are tagged with. + UID string `yaml:"uid"` + // description: | + // Endpoint the cluster can be reached at. + ClusterEndpoint string `yaml:"clusterEndpoint"` + // description: | + // Secret used to authenticate the bootstrapping node. + InitSecret []byte `yaml:"initSecret"` + // description: | + // List of Subject Alternative Names (SANs) to add to the Kubernetes API server certificate. + // If no SANs should be added, this field can be left empty. + APIServerCertSANs []string `yaml:"apiServerCertSANs"` + // description: | + // Name used in the cluster's named resources. + Name string `yaml:"name"` + // description: | + // Values specific to a Constellation cluster running on Azure. + Azure *Azure `yaml:"azure,omitempty"` + // description: | + // Values specific to a Constellation cluster running on GCP. + GCP *GCP `yaml:"gcp,omitempty"` +} + +// GCP describes the infra state related to GCP. +type GCP struct { + // description: | + // Project ID of the GCP project the cluster is running in. + ProjectID string `yaml:"projectID"` + // description: | + // CIDR range of the cluster's nodes. + IPCidrNode string `yaml:"ipCidrNode"` + // description: | + // CIDR range of the cluster's pods. + IPCidrPod string `yaml:"ipCidrPod"` +} + +// Azure describes the infra state related to Azure. +type Azure struct { + // description: | + // Resource Group the cluster's resources are placed in. + ResourceGroup string `yaml:"resourceGroup"` + // description: | + // ID of the Azure subscription the cluster is running in. + SubscriptionID string `yaml:"subscriptionID"` + // description: | + // Security group name of the cluster's resource group. + NetworkSecurityGroupName string `yaml:"networkSecurityGroupName"` + // description: | + // Name of the cluster's load balancer. + LoadBalancerName string `yaml:"loadBalancerName"` + // description: | + // ID of the UAMI the cluster's nodes are running with. + UserAssignedIdentity string `yaml:"userAssignedIdentity"` + // description: | + // MAA endpoint that can be used as a fallback for veryifying the ID key digests + // in the cluster's attestation report if the enforcement policy is set accordingly. + // Can be left empty otherwise. + AttestationURL string `yaml:"attestationURL"` } // New creates a new cluster state (file). @@ -70,42 +164,3 @@ func (s *State) Merge(other *State) (*State, error) { } return s, nil } - -// ClusterValues describe the (Kubernetes) cluster state, set during initialization of the cluster. -type ClusterValues struct { - // ClusterID is the unique identifier of the cluster. - ClusterID string `yaml:"clusterID"` - // OwnerID is the unique identifier of the owner of the cluster. - OwnerID string `yaml:"ownerID"` - // MeasurementSalt is the salt generated during cluster init. - MeasurementSalt []byte `yaml:"measurementSalt"` -} - -// Infrastructure describe the state related to the cloud resources of the cluster. -type Infrastructure struct { - UID string `yaml:"uid"` - ClusterEndpoint string `yaml:"clusterEndpoint"` - InitSecret []byte `yaml:"initSecret"` - APIServerCertSANs []string `yaml:"apiServerCertSANs"` - // Name is the name of the cluster. - Name string `yaml:"name"` - Azure *Azure `yaml:"azure,omitempty"` - GCP *GCP `yaml:"gcp,omitempty"` -} - -// GCP describes the infra state related to GCP. -type GCP struct { - ProjectID string `yaml:"projectID"` - IPCidrNode string `yaml:"ipCidrNode"` - IPCidrPod string `yaml:"ipCidrPod"` -} - -// Azure describes the infra state related to Azure. -type Azure struct { - ResourceGroup string `yaml:"resourceGroup"` - SubscriptionID string `yaml:"subscriptionID"` - NetworkSecurityGroupName string `yaml:"networkSecurityGroupName"` - LoadBalancerName string `yaml:"loadBalancerName"` - UserAssignedIdentity string `yaml:"userAssignedIdentity"` - AttestationURL string `yaml:"attestationURL"` -} diff --git a/cli/internal/state/state_doc.go b/cli/internal/state/state_doc.go new file mode 100644 index 000000000..c57088ac3 --- /dev/null +++ b/cli/internal/state/state_doc.go @@ -0,0 +1,215 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Code generated by hack/docgen tool. DO NOT EDIT. + +package state + +import ( + "github.com/siderolabs/talos/pkg/machinery/config/encoder" +) + +var ( + StateDoc encoder.Doc + ClusterValuesDoc encoder.Doc + InfrastructureDoc encoder.Doc + GCPDoc encoder.Doc + AzureDoc encoder.Doc +) + +func init() { + StateDoc.Type = "State" + StateDoc.Comments[encoder.LineComment] = "State describe the entire state to describe a Constellation cluster." + StateDoc.Description = "State describe the entire state to describe a Constellation cluster." + StateDoc.Fields = make([]encoder.Doc, 3) + StateDoc.Fields[0].Name = "version" + StateDoc.Fields[0].Type = "string" + StateDoc.Fields[0].Note = "" + StateDoc.Fields[0].Description = "Schema version of this state file." + StateDoc.Fields[0].Comments[encoder.LineComment] = "Schema version of this state file." + StateDoc.Fields[1].Name = "infrastructure" + StateDoc.Fields[1].Type = "Infrastructure" + StateDoc.Fields[1].Note = "" + StateDoc.Fields[1].Description = "State of the cluster's cloud resources. These values are retrieved during\ncluster creation. In the case of self-managed infrastructure, the marked\nfields in this struct should be filled by the user as per\nhttps://docs.edgeless.systems/constellation/workflows/create." + StateDoc.Fields[1].Comments[encoder.LineComment] = "State of the cluster's cloud resources. These values are retrieved during" + StateDoc.Fields[2].Name = "clusterValues" + StateDoc.Fields[2].Type = "ClusterValues" + StateDoc.Fields[2].Note = "" + StateDoc.Fields[2].Description = "DO NOT EDIT. State of the Constellation Kubernetes cluster.\nThese values are set during cluster initialization and should not be changed." + StateDoc.Fields[2].Comments[encoder.LineComment] = "DO NOT EDIT. State of the Constellation Kubernetes cluster." + + ClusterValuesDoc.Type = "ClusterValues" + ClusterValuesDoc.Comments[encoder.LineComment] = "ClusterValues describe the (Kubernetes) cluster state, set during initialization of the cluster." + ClusterValuesDoc.Description = "ClusterValues describe the (Kubernetes) cluster state, set during initialization of the cluster." + ClusterValuesDoc.AppearsIn = []encoder.Appearance{ + { + TypeName: "State", + FieldName: "clusterValues", + }, + } + ClusterValuesDoc.Fields = make([]encoder.Doc, 3) + ClusterValuesDoc.Fields[0].Name = "clusterID" + ClusterValuesDoc.Fields[0].Type = "string" + ClusterValuesDoc.Fields[0].Note = "" + ClusterValuesDoc.Fields[0].Description = "Unique identifier of the cluster." + ClusterValuesDoc.Fields[0].Comments[encoder.LineComment] = "Unique identifier of the cluster." + ClusterValuesDoc.Fields[1].Name = "ownerID" + ClusterValuesDoc.Fields[1].Type = "string" + ClusterValuesDoc.Fields[1].Note = "" + ClusterValuesDoc.Fields[1].Description = "Unique identifier of the owner of the cluster." + ClusterValuesDoc.Fields[1].Comments[encoder.LineComment] = "Unique identifier of the owner of the cluster." + ClusterValuesDoc.Fields[2].Name = "measurementSalt" + ClusterValuesDoc.Fields[2].Type = "[]byte" + ClusterValuesDoc.Fields[2].Note = "" + ClusterValuesDoc.Fields[2].Description = "Salt used to generate the ClusterID on the bootstrapping node." + ClusterValuesDoc.Fields[2].Comments[encoder.LineComment] = "Salt used to generate the ClusterID on the bootstrapping node." + + InfrastructureDoc.Type = "Infrastructure" + InfrastructureDoc.Comments[encoder.LineComment] = "Infrastructure describe the state related to the cloud resources of the cluster." + InfrastructureDoc.Description = "Infrastructure describe the state related to the cloud resources of the cluster." + InfrastructureDoc.AppearsIn = []encoder.Appearance{ + { + TypeName: "State", + FieldName: "infrastructure", + }, + } + InfrastructureDoc.Fields = make([]encoder.Doc, 7) + InfrastructureDoc.Fields[0].Name = "uid" + InfrastructureDoc.Fields[0].Type = "string" + InfrastructureDoc.Fields[0].Note = "" + InfrastructureDoc.Fields[0].Description = "Unique identifier the cluster's cloud resources are tagged with." + InfrastructureDoc.Fields[0].Comments[encoder.LineComment] = "Unique identifier the cluster's cloud resources are tagged with." + InfrastructureDoc.Fields[1].Name = "clusterEndpoint" + InfrastructureDoc.Fields[1].Type = "string" + InfrastructureDoc.Fields[1].Note = "" + InfrastructureDoc.Fields[1].Description = "Endpoint the cluster can be reached at." + InfrastructureDoc.Fields[1].Comments[encoder.LineComment] = "Endpoint the cluster can be reached at." + InfrastructureDoc.Fields[2].Name = "initSecret" + InfrastructureDoc.Fields[2].Type = "[]byte" + InfrastructureDoc.Fields[2].Note = "" + InfrastructureDoc.Fields[2].Description = "Secret used to authenticate the bootstrapping node." + InfrastructureDoc.Fields[2].Comments[encoder.LineComment] = "Secret used to authenticate the bootstrapping node." + InfrastructureDoc.Fields[3].Name = "apiServerCertSANs" + InfrastructureDoc.Fields[3].Type = "[]string" + InfrastructureDoc.Fields[3].Note = "" + InfrastructureDoc.Fields[3].Description = "description: |\n List of Subject Alternative Names (SANs) to add to the Kubernetes API server certificate.\n If no SANs should be added, this field can be left empty.\n" + InfrastructureDoc.Fields[3].Comments[encoder.LineComment] = "description: |" + InfrastructureDoc.Fields[4].Name = "name" + InfrastructureDoc.Fields[4].Type = "string" + InfrastructureDoc.Fields[4].Note = "" + InfrastructureDoc.Fields[4].Description = "Name used in the cluster's named resources." + InfrastructureDoc.Fields[4].Comments[encoder.LineComment] = "Name used in the cluster's named resources." + InfrastructureDoc.Fields[5].Name = "azure" + InfrastructureDoc.Fields[5].Type = "Azure" + InfrastructureDoc.Fields[5].Note = "" + InfrastructureDoc.Fields[5].Description = "Values specific to a Constellation cluster running on Azure." + InfrastructureDoc.Fields[5].Comments[encoder.LineComment] = "Values specific to a Constellation cluster running on Azure." + InfrastructureDoc.Fields[6].Name = "gcp" + InfrastructureDoc.Fields[6].Type = "GCP" + InfrastructureDoc.Fields[6].Note = "" + InfrastructureDoc.Fields[6].Description = "Values specific to a Constellation cluster running on GCP." + InfrastructureDoc.Fields[6].Comments[encoder.LineComment] = "Values specific to a Constellation cluster running on GCP." + + GCPDoc.Type = "GCP" + GCPDoc.Comments[encoder.LineComment] = "GCP describes the infra state related to GCP." + GCPDoc.Description = "GCP describes the infra state related to GCP." + GCPDoc.AppearsIn = []encoder.Appearance{ + { + TypeName: "Infrastructure", + FieldName: "gcp", + }, + } + GCPDoc.Fields = make([]encoder.Doc, 3) + GCPDoc.Fields[0].Name = "projectID" + GCPDoc.Fields[0].Type = "string" + GCPDoc.Fields[0].Note = "" + GCPDoc.Fields[0].Description = "Project ID of the GCP project the cluster is running in." + GCPDoc.Fields[0].Comments[encoder.LineComment] = "Project ID of the GCP project the cluster is running in." + GCPDoc.Fields[1].Name = "ipCidrNode" + GCPDoc.Fields[1].Type = "string" + GCPDoc.Fields[1].Note = "" + GCPDoc.Fields[1].Description = "CIDR range of the cluster's nodes." + GCPDoc.Fields[1].Comments[encoder.LineComment] = "CIDR range of the cluster's nodes." + GCPDoc.Fields[2].Name = "ipCidrPod" + GCPDoc.Fields[2].Type = "string" + GCPDoc.Fields[2].Note = "" + GCPDoc.Fields[2].Description = "CIDR range of the cluster's pods." + GCPDoc.Fields[2].Comments[encoder.LineComment] = "CIDR range of the cluster's pods." + + AzureDoc.Type = "Azure" + AzureDoc.Comments[encoder.LineComment] = "Azure describes the infra state related to Azure." + AzureDoc.Description = "Azure describes the infra state related to Azure." + AzureDoc.AppearsIn = []encoder.Appearance{ + { + TypeName: "Infrastructure", + FieldName: "azure", + }, + } + AzureDoc.Fields = make([]encoder.Doc, 6) + AzureDoc.Fields[0].Name = "resourceGroup" + AzureDoc.Fields[0].Type = "string" + AzureDoc.Fields[0].Note = "" + AzureDoc.Fields[0].Description = "Resource Group the cluster's resources are placed in." + AzureDoc.Fields[0].Comments[encoder.LineComment] = "Resource Group the cluster's resources are placed in." + AzureDoc.Fields[1].Name = "subscriptionID" + AzureDoc.Fields[1].Type = "string" + AzureDoc.Fields[1].Note = "" + AzureDoc.Fields[1].Description = "ID of the Azure subscription the cluster is running in." + AzureDoc.Fields[1].Comments[encoder.LineComment] = "ID of the Azure subscription the cluster is running in." + AzureDoc.Fields[2].Name = "networkSecurityGroupName" + AzureDoc.Fields[2].Type = "string" + AzureDoc.Fields[2].Note = "" + AzureDoc.Fields[2].Description = "Security group name of the cluster's resource group." + AzureDoc.Fields[2].Comments[encoder.LineComment] = "Security group name of the cluster's resource group." + AzureDoc.Fields[3].Name = "loadBalancerName" + AzureDoc.Fields[3].Type = "string" + AzureDoc.Fields[3].Note = "" + AzureDoc.Fields[3].Description = "Name of the cluster's load balancer." + AzureDoc.Fields[3].Comments[encoder.LineComment] = "Name of the cluster's load balancer." + AzureDoc.Fields[4].Name = "userAssignedIdentity" + AzureDoc.Fields[4].Type = "string" + AzureDoc.Fields[4].Note = "" + AzureDoc.Fields[4].Description = "ID of the UAMI the cluster's nodes are running with." + AzureDoc.Fields[4].Comments[encoder.LineComment] = "ID of the UAMI the cluster's nodes are running with." + AzureDoc.Fields[5].Name = "attestationURL" + AzureDoc.Fields[5].Type = "string" + AzureDoc.Fields[5].Note = "" + AzureDoc.Fields[5].Description = "MAA endpoint that can be used as a fallback for veryifying the ID key digests\nin the cluster's attestation report if the enforcement policy is set accordingly.\nCan be left empty otherwise." + AzureDoc.Fields[5].Comments[encoder.LineComment] = "MAA endpoint that can be used as a fallback for veryifying the ID key digests" +} + +func (_ State) Doc() *encoder.Doc { + return &StateDoc +} + +func (_ ClusterValues) Doc() *encoder.Doc { + return &ClusterValuesDoc +} + +func (_ Infrastructure) Doc() *encoder.Doc { + return &InfrastructureDoc +} + +func (_ GCP) Doc() *encoder.Doc { + return &GCPDoc +} + +func (_ Azure) Doc() *encoder.Doc { + return &AzureDoc +} + +// GetConfigurationDoc returns documentation for the file ./state_doc.go. +func GetConfigurationDoc() *encoder.FileDoc { + return &encoder.FileDoc{ + Name: "Configuration", + Description: "package state defines the structure of the Constellation state file.\n", + Structs: []*encoder.Doc{ + &StateDoc, + &ClusterValuesDoc, + &InfrastructureDoc, + &GCPDoc, + &AzureDoc, + }, + } +} diff --git a/cli/internal/state/state_test.go b/cli/internal/state/state_test.go index dd7c305bb..96311977e 100644 --- a/cli/internal/state/state_test.go +++ b/cli/internal/state/state_test.go @@ -11,9 +11,9 @@ import ( "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" + "github.com/siderolabs/talos/pkg/machinery/config/encoder" "github.com/spf13/afero" "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" ) var defaultState = &State{ @@ -145,7 +145,7 @@ func TestReadFromFile(t *testing.T) { func mustMarshalYaml(t *testing.T, v any) []byte { t.Helper() - b, err := yaml.Marshal(v) + b, err := encoder.NewEncoder(v).Encode() if err != nil { t.Fatalf("failed to marshal yaml: %v", err) }