2018-05-22 21:43:36 +02:00
|
|
|
// Copyright 2016-2018, Pulumi Corporation.
|
|
|
|
//
|
|
|
|
// 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.
|
2017-03-01 00:43:46 +01:00
|
|
|
|
2017-10-16 21:04:35 +02:00
|
|
|
// Package stack contains the serialized and configurable state associated with an stack; or, in other
|
Overhaul resources, planning, and environments
This change, part of pulumi/lumi#90, overhauls quite a bit of the
core resource, planning, environments, and related areas.
The biggest amount of movement comes from the splitting of pkg/resource
into multiple sub-packages. This results in:
- pkg/resource: just the core resource data structures.
- pkg/resource/deployment: all planning and deployment logic.
- pkg/resource/environment: all environment, configuration, and
serialized checkpoint structures and logic.
- pkg/resource/plugin: all dynamically loaded analyzer and
provider logic, including the actual loading and RPC mechanisms.
This also splits the resource abstraction up. We now have:
- resource.Resource: a shared interface.
- resource.Object: a resource that is connected to a live object
that will periodically observe mutations due to ongoing
evaluation of computations. Snapshots of its state may be
taken; however, this is purely a "pre-planning" abstraction.
- resource.State: a snapshot of a resource's state that is frozen.
In other words, it is no longer connected to a live object.
This is what will store provider outputs (ID and properties),
and is what may be serialized into a deployment record.
The branch is in a half-baked state as of this change; more changes
are to come...
2017-06-09 01:37:40 +02:00
|
|
|
// words, a deployment target. It pertains to resources and deployment plans, but is a package unto itself.
|
2017-10-16 21:04:35 +02:00
|
|
|
package stack
|
2017-03-01 00:43:46 +01:00
|
|
|
|
|
|
|
import (
|
2017-10-28 04:42:17 +02:00
|
|
|
"encoding/json"
|
|
|
|
|
2018-03-08 19:52:05 +01:00
|
|
|
"github.com/pkg/errors"
|
2018-02-06 18:57:32 +01:00
|
|
|
|
2018-02-28 19:02:02 +01:00
|
|
|
"github.com/pulumi/pulumi/pkg/apitype"
|
2018-08-03 23:06:00 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/apitype/migrate"
|
2017-09-22 04:18:21 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/resource"
|
2017-10-19 00:37:18 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/resource/config"
|
2017-09-22 04:18:21 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/resource/deploy"
|
|
|
|
"github.com/pulumi/pulumi/pkg/tokens"
|
|
|
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
2017-03-01 00:43:46 +01:00
|
|
|
)
|
|
|
|
|
2018-08-03 23:06:00 +02:00
|
|
|
func UnmarshalVersionedCheckpointToLatestCheckpoint(bytes []byte) (*apitype.CheckpointV2, error) {
|
2018-03-08 19:52:05 +01:00
|
|
|
var versionedCheckpoint apitype.VersionedCheckpoint
|
|
|
|
if err := json.Unmarshal(bytes, &versionedCheckpoint); err != nil {
|
2017-10-28 04:42:17 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-03-08 19:52:05 +01:00
|
|
|
|
|
|
|
switch versionedCheckpoint.Version {
|
|
|
|
case 0:
|
|
|
|
// The happens when we are loading a checkpoint file from before we started to version things. Go's
|
|
|
|
// json package did not support strict marshalling before 1.10, and we use 1.9 in our toolchain today.
|
|
|
|
// After we upgrade, we could consider rewriting this code to use DisallowUnknownFields() on the decoder
|
|
|
|
// to have the old checkpoint not even deserialize as an apitype.VersionedCheckpoint.
|
2018-08-03 23:06:00 +02:00
|
|
|
var v1checkpoint apitype.CheckpointV1
|
|
|
|
if err := json.Unmarshal(bytes, &v1checkpoint); err != nil {
|
2018-03-08 19:52:05 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-20 22:31:41 +02:00
|
|
|
|
2018-08-03 23:06:00 +02:00
|
|
|
checkpoint := migrate.UpToCheckpointV2(v1checkpoint)
|
2018-07-27 23:22:16 +02:00
|
|
|
return &checkpoint, nil
|
2018-03-08 19:52:05 +01:00
|
|
|
case 1:
|
2018-08-03 23:06:00 +02:00
|
|
|
var v1checkpoint apitype.CheckpointV1
|
|
|
|
if err := json.Unmarshal(versionedCheckpoint.Checkpoint, &v1checkpoint); err != nil {
|
2018-03-08 19:52:05 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-20 22:31:41 +02:00
|
|
|
|
2018-08-03 23:06:00 +02:00
|
|
|
checkpoint := migrate.UpToCheckpointV2(v1checkpoint)
|
2018-03-08 19:52:05 +01:00
|
|
|
return &checkpoint, nil
|
2018-08-03 23:06:00 +02:00
|
|
|
case 2:
|
|
|
|
var v2checkpoint apitype.CheckpointV2
|
|
|
|
if err := json.Unmarshal(versionedCheckpoint.Checkpoint, &v2checkpoint); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &v2checkpoint, nil
|
2018-03-08 19:52:05 +01:00
|
|
|
default:
|
|
|
|
return nil, errors.Errorf("unsupported checkpoint version %d", versionedCheckpoint.Version)
|
|
|
|
}
|
2017-10-28 04:42:17 +02:00
|
|
|
}
|
2017-03-01 00:43:46 +01:00
|
|
|
|
2017-10-28 04:42:17 +02:00
|
|
|
// SerializeCheckpoint turns a snapshot into a data structure suitable for serialization.
|
2018-03-08 19:52:05 +01:00
|
|
|
func SerializeCheckpoint(stack tokens.QName, config config.Map, snap *deploy.Snapshot) *apitype.VersionedCheckpoint {
|
2017-03-01 00:43:46 +01:00
|
|
|
// If snap is nil, that's okay, we will just create an empty deployment; otherwise, serialize the whole snapshot.
|
2018-08-03 23:06:00 +02:00
|
|
|
var latest *apitype.DeploymentV2
|
2017-03-01 00:43:46 +01:00
|
|
|
if snap != nil {
|
Make more progress on the new deployment model
This change restructures a lot more pertaining to deployments, snapshots,
environments, and the like.
The most notable change is that the notion of a deploy.Source is introduced,
which splits the responsibility between the deploy.Plan -- which simply
understands how to compute and carry out deployment plans -- and the idea
of something that can produce new objects on-demand during deployment.
The primary such implementation is evalSource, which encapsulates an
interpreter and takes a package, args, and config map, and proceeds to run
the interpreter in a distinct goroutine. It synchronizes as needed to
poke and prod the interpreter along its path to create new resource objects.
There are two other sources, however. First, a nullSource, which simply
refuses to create new objects. This can be handy when writing isolated
tests but is also used to simulate the "empty" environment as necessary to
do a complete teardown of the target environment. Second, a fixedSource,
which takes a pre-computed array of objects, and hands those, in order, to
the planning engine; this is mostly useful as a testing technique.
Boatloads of code is now changed and updated in the various CLI commands.
This further chugs along towards pulumi/lumi#90. The end is in sight.
2017-06-10 20:50:47 +02:00
|
|
|
latest = SerializeDeployment(snap)
|
2017-03-01 00:43:46 +01:00
|
|
|
}
|
|
|
|
|
2018-08-03 23:06:00 +02:00
|
|
|
b, err := json.Marshal(apitype.CheckpointV2{
|
Improve the overall cloud CLI experience
This improves the overall cloud CLI experience workflow.
Now whether a stack is local or cloud is inherent to the stack
itself. If you interact with a cloud stack, we transparently talk
to the cloud; if you interact with a local stack, we just do the
right thing, and perform all operations locally. Aside from sometimes
seeing a cloud emoji pop-up ☁️, the experience is quite similar.
For example, to initialize a new cloud stack, simply:
$ pulumi login
Logging into Pulumi Cloud: https://pulumi.com/
Enter Pulumi access token: <enter your token>
$ pulumi stack init my-cloud-stack
Note that you may log into a specific cloud if you'd like. For
now, this is just for our own testing purposes, but someday when we
support custom clouds (e.g., Enterprise), you can just say:
$ pulumi login --cloud-url https://corp.acme.my-ppc.net:9873
The cloud is now the default. If you instead prefer a "fire and
forget" style of stack, you can skip the login and pass `--local`:
$ pulumi stack init my-faf-stack --local
If you are logged in and run `pulumi`, we tell you as much:
$ pulumi
Usage:
pulumi [command]
// as before...
Currently logged into the Pulumi Cloud ☁️
https://pulumi.com/
And if you list your stacks, we tell you which one is local or not:
$ pulumi stack ls
NAME LAST UPDATE RESOURCE COUNT CLOUD URL
my-cloud-stack 2017-12-01 ... 3 https://pulumi.com/
my-faf-stack n/a 0 n/a
And `pulumi stack` by itself prints information like your cloud org,
PPC name, and so on, in addition to the usuals.
I shall write up more details and make sure to document these changes.
This change also fairly significantly refactors the layout of cloud
versus local logic, so that the cmd/ package is resonsible for CLI
things, and the new pkg/backend/ package is responsible for the
backends. The following is the overall resulting package architecture:
* The backend.Backend interface can be implemented to substitute
a new backend. This has operations to get and list stacks,
perform updates, and so on.
* The backend.Stack struct is a wrapper around a stack that has
or is being manipulated by a Backend. It resembles our existing
Stack notions in the engine, but carries additional metadata
about its source. Notably, it offers functions that allow
operations like updating and deleting on the Backend from which
it came.
* There is very little else in the pkg/backend/ package.
* A new package, pkg/backend/local/, encapsulates all local state
management for "fire and forget" scenarios. It simply implements
the above logic and contains anything specific to the local
experience.
* A peer package, pkg/backend/cloud/, encapsulates all logic
required for the cloud experience. This includes its subpackage
apitype/ which contains JSON schema descriptions required for
REST calls against the cloud backend. It also contains handy
functions to list which clouds we have authenticated with.
* A subpackage here, pkg/backend/state/, is not a provider at all.
Instead, it contains all of the state management functions that
are currently shared between local and cloud backends. This
includes configuration logic -- including encryption -- as well
as logic pertaining to which stacks are known to the workspace.
This addresses pulumi/pulumi#629 and pulumi/pulumi#494.
2017-12-02 16:29:46 +01:00
|
|
|
Stack: stack,
|
2017-10-19 00:37:18 +02:00
|
|
|
Config: config,
|
2017-03-01 00:43:46 +01:00
|
|
|
Latest: latest,
|
2018-03-08 19:52:05 +01:00
|
|
|
})
|
|
|
|
contract.AssertNoError(err)
|
|
|
|
|
|
|
|
return &apitype.VersionedCheckpoint{
|
2018-05-25 22:29:59 +02:00
|
|
|
Version: apitype.DeploymentSchemaVersionCurrent,
|
2018-03-08 19:52:05 +01:00
|
|
|
Checkpoint: json.RawMessage(b),
|
2017-03-01 00:43:46 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-03 23:06:00 +02:00
|
|
|
// DeserializeCheckpoint takes a serialized deployment record and returns its associated snapshot. Returns nil
|
|
|
|
// if there have been no deployments performed on this checkpoint.
|
|
|
|
func DeserializeCheckpoint(chkpoint *apitype.CheckpointV2) (*deploy.Snapshot, error) {
|
Make more progress on the new deployment model
This change restructures a lot more pertaining to deployments, snapshots,
environments, and the like.
The most notable change is that the notion of a deploy.Source is introduced,
which splits the responsibility between the deploy.Plan -- which simply
understands how to compute and carry out deployment plans -- and the idea
of something that can produce new objects on-demand during deployment.
The primary such implementation is evalSource, which encapsulates an
interpreter and takes a package, args, and config map, and proceeds to run
the interpreter in a distinct goroutine. It synchronizes as needed to
poke and prod the interpreter along its path to create new resource objects.
There are two other sources, however. First, a nullSource, which simply
refuses to create new objects. This can be handy when writing isolated
tests but is also used to simulate the "empty" environment as necessary to
do a complete teardown of the target environment. Second, a fixedSource,
which takes a pre-computed array of objects, and hands those, in order, to
the planning engine; this is mostly useful as a testing technique.
Boatloads of code is now changed and updated in the various CLI commands.
This further chugs along towards pulumi/lumi#90. The end is in sight.
2017-06-10 20:50:47 +02:00
|
|
|
contract.Require(chkpoint != nil, "chkpoint")
|
2018-08-03 23:06:00 +02:00
|
|
|
if chkpoint.Latest != nil {
|
|
|
|
return DeserializeDeploymentV2(*chkpoint.Latest)
|
2017-03-01 00:43:46 +01:00
|
|
|
}
|
|
|
|
|
2018-08-03 23:06:00 +02:00
|
|
|
return nil, nil
|
2017-03-01 00:43:46 +01:00
|
|
|
}
|
2017-12-14 01:09:14 +01:00
|
|
|
|
|
|
|
// GetRootStackResource returns the root stack resource from a given snapshot, or nil if not found. If the stack
|
|
|
|
// exists, its output properties, if any, are also returned in the resulting map.
|
|
|
|
func GetRootStackResource(snap *deploy.Snapshot) (*resource.State, map[string]interface{}) {
|
|
|
|
if snap != nil {
|
|
|
|
for _, res := range snap.Resources {
|
|
|
|
if res.Type == resource.RootStackType {
|
|
|
|
return res, SerializeResource(res).Outputs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|